diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/modules/test/src/test_memory_entry.c b/modules/test/src/test_memory_entry.c new file mode 100644 index 0000000..e883e07 --- /dev/null +++ b/modules/test/src/test_memory_entry.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_entry_new(void); +static void test_memory_entry_new_alignment(void); +static void test_memory_entry_new_realloc(void); +static void test_memory_entry_set_null(void); + +/** + * memory_entry 単体テストスイート + */ +void suite_memory_entry(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_entry new/delete", test_memory_entry_new); + ut->add(ut, UT_TESTCASE, "memory_entry new (alignment)", test_memory_entry_new_alignment); + ut->add(ut, UT_TESTCASE, "memory_entry new (realloc)", test_memory_entry_new_realloc); + ut->add(ut, UT_TESTCASE, "memory_entry set (NULL Entry)", test_memory_entry_set_null); +} + +/** + * memory_entry 生成/破棄 + * + * @process KcMemoryEntry_new を用いて KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, 0, 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + + KcMemoryEntry_delete(entry); + // entry->mark には、KC_MEMORY_DELETED を格納してから破棄されているが、 + // free 後のメモリ領域は、確認不可。 +} + +/** + * memory_entry 生成 (alignment指定) + * + * @process alignment を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new_alignment(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + KcMemoryEntry_delete(entry); +} + +/** + * memory_entry 生成 (entry 指定) + * + * @process entry を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に更新されていること。 + */ +static void test_memory_entry_new_realloc(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + KcMemoryEntry *new_entry = KcMemoryEntry_new( + entry, 0, 250, KC_MEMORY_ALLOCATED, "test_file2", "test_func2", 123); + assert_not_null(new_entry); + assert_equals(KC_MEMORY_ALLOCATED, new_entry->mark); + assert_equals(250, new_entry->size); + assert_equals("test_file2", new_entry->file); + assert_equals("test_func2", new_entry->func); + assert_equals(123, new_entry->line); + + KcMemoryEntry_delete(new_entry); +} + +/** + * memory_entry 設定 + * + * @process NULL を指定して、KcMemoryEntry_set を用いて各値を設定する。 + * @result エントリが NULL のため、何も処理されないこと。 + */ +static void test_memory_entry_set_null(void) +{ + KcMemoryEntry_set(NULL, + 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 123); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/modules/test/src/test_memory_entry.c b/modules/test/src/test_memory_entry.c new file mode 100644 index 0000000..e883e07 --- /dev/null +++ b/modules/test/src/test_memory_entry.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_entry_new(void); +static void test_memory_entry_new_alignment(void); +static void test_memory_entry_new_realloc(void); +static void test_memory_entry_set_null(void); + +/** + * memory_entry 単体テストスイート + */ +void suite_memory_entry(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_entry new/delete", test_memory_entry_new); + ut->add(ut, UT_TESTCASE, "memory_entry new (alignment)", test_memory_entry_new_alignment); + ut->add(ut, UT_TESTCASE, "memory_entry new (realloc)", test_memory_entry_new_realloc); + ut->add(ut, UT_TESTCASE, "memory_entry set (NULL Entry)", test_memory_entry_set_null); +} + +/** + * memory_entry 生成/破棄 + * + * @process KcMemoryEntry_new を用いて KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, 0, 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + + KcMemoryEntry_delete(entry); + // entry->mark には、KC_MEMORY_DELETED を格納してから破棄されているが、 + // free 後のメモリ領域は、確認不可。 +} + +/** + * memory_entry 生成 (alignment指定) + * + * @process alignment を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new_alignment(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + KcMemoryEntry_delete(entry); +} + +/** + * memory_entry 生成 (entry 指定) + * + * @process entry を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に更新されていること。 + */ +static void test_memory_entry_new_realloc(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + KcMemoryEntry *new_entry = KcMemoryEntry_new( + entry, 0, 250, KC_MEMORY_ALLOCATED, "test_file2", "test_func2", 123); + assert_not_null(new_entry); + assert_equals(KC_MEMORY_ALLOCATED, new_entry->mark); + assert_equals(250, new_entry->size); + assert_equals("test_file2", new_entry->file); + assert_equals("test_func2", new_entry->func); + assert_equals(123, new_entry->line); + + KcMemoryEntry_delete(new_entry); +} + +/** + * memory_entry 設定 + * + * @process NULL を指定して、KcMemoryEntry_set を用いて各値を設定する。 + * @result エントリが NULL のため、何も処理されないこと。 + */ +static void test_memory_entry_set_null(void) +{ + KcMemoryEntry_set(NULL, + 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 123); +} diff --git a/modules/test/src/test_memory_listener.c b/modules/test/src/test_memory_listener.c new file mode 100644 index 0000000..5927064 --- /dev/null +++ b/modules/test/src/test_memory_listener.c @@ -0,0 +1,146 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_listener_allocate(void); +static void test_memory_listener_free(void); +static void test_memory_listener_error(void); +static void test_memory_listener_dump_allocate(void); +static void test_memory_listener_dump_allocate_null(void); +static void test_memory_listener_dump_free(void); +static void test_memory_listener_dump_free_null(void); +static void test_memory_listener_dump_error(void); +static void test_memory_listener_dump_error_null(void); + +/** + * memory_listner 単体テストスイート + */ +void suite_memory_listener(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_listener_allcate", test_memory_listener_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_free", test_memory_listener_free); + ut->add(ut, UT_TESTCASE, "memory_listener_error", test_memory_listener_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate", test_memory_listener_dump_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate(NULL)", test_memory_listener_dump_allocate_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free", test_memory_listener_dump_free); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free(NULL)", test_memory_listener_dump_free_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error", test_memory_listener_dump_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error(NULL)", test_memory_listener_dump_error_null); +} + +static char test_data[] = "ABCDEFG \x00\x81 0123456789"; +static KcMemoryEntry test_entry = { + .size = 14, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + +/** + * KcMemoryListner_allocate 動作確認 + * + * @process KcMemoryListener_allocate を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_allocate(void) +{ + KcMemoryListener_allocate(&test_entry); +} + +/** + * KcMemoryListner_free 動作確認 + * + * @process KcMemoryListener_free を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_free(void) +{ + KcMemoryListener_free(&test_entry); +} + +/** + * KcMemoryListner_error 動作確認 + * + * @process KcMemoryListener_error を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_error(void) +{ + KcMemoryListener_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_allocate 動作確認 + * + * @process KcMemoryListener_dump_allocate を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_allocate(void) +{ + KcMemoryListener_dump_allocate(&test_entry); +} + +/** + * KcMemoryListner_dump_allocate (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_allocate を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_allocate_null(void) +{ + KcMemoryListener_dump_allocate(NULL); +} + +/** + * KcMemoryListner_dump_free 動作確認 + * + * @process KcMemoryListener_dump_free を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_free(void) +{ + KcMemoryListener_dump_free(&test_entry); +} + +/** + * KcMemoryListner_dump_free (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_free を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_free_null(void) +{ + KcMemoryListener_dump_free(NULL); +} + +/** + * KcMemoryListner_dump_error 動作確認 + * + * @process KcMemoryListener_dump_error を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_error(void) +{ + KcMemoryListener_dump_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_error (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_error を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_error_null(void) +{ + KcMemoryListener_dump_error(NULL, "ERROR\n"); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/modules/test/src/test_memory_entry.c b/modules/test/src/test_memory_entry.c new file mode 100644 index 0000000..e883e07 --- /dev/null +++ b/modules/test/src/test_memory_entry.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_entry_new(void); +static void test_memory_entry_new_alignment(void); +static void test_memory_entry_new_realloc(void); +static void test_memory_entry_set_null(void); + +/** + * memory_entry 単体テストスイート + */ +void suite_memory_entry(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_entry new/delete", test_memory_entry_new); + ut->add(ut, UT_TESTCASE, "memory_entry new (alignment)", test_memory_entry_new_alignment); + ut->add(ut, UT_TESTCASE, "memory_entry new (realloc)", test_memory_entry_new_realloc); + ut->add(ut, UT_TESTCASE, "memory_entry set (NULL Entry)", test_memory_entry_set_null); +} + +/** + * memory_entry 生成/破棄 + * + * @process KcMemoryEntry_new を用いて KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, 0, 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + + KcMemoryEntry_delete(entry); + // entry->mark には、KC_MEMORY_DELETED を格納してから破棄されているが、 + // free 後のメモリ領域は、確認不可。 +} + +/** + * memory_entry 生成 (alignment指定) + * + * @process alignment を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new_alignment(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + KcMemoryEntry_delete(entry); +} + +/** + * memory_entry 生成 (entry 指定) + * + * @process entry を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に更新されていること。 + */ +static void test_memory_entry_new_realloc(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + KcMemoryEntry *new_entry = KcMemoryEntry_new( + entry, 0, 250, KC_MEMORY_ALLOCATED, "test_file2", "test_func2", 123); + assert_not_null(new_entry); + assert_equals(KC_MEMORY_ALLOCATED, new_entry->mark); + assert_equals(250, new_entry->size); + assert_equals("test_file2", new_entry->file); + assert_equals("test_func2", new_entry->func); + assert_equals(123, new_entry->line); + + KcMemoryEntry_delete(new_entry); +} + +/** + * memory_entry 設定 + * + * @process NULL を指定して、KcMemoryEntry_set を用いて各値を設定する。 + * @result エントリが NULL のため、何も処理されないこと。 + */ +static void test_memory_entry_set_null(void) +{ + KcMemoryEntry_set(NULL, + 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 123); +} diff --git a/modules/test/src/test_memory_listener.c b/modules/test/src/test_memory_listener.c new file mode 100644 index 0000000..5927064 --- /dev/null +++ b/modules/test/src/test_memory_listener.c @@ -0,0 +1,146 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_listener_allocate(void); +static void test_memory_listener_free(void); +static void test_memory_listener_error(void); +static void test_memory_listener_dump_allocate(void); +static void test_memory_listener_dump_allocate_null(void); +static void test_memory_listener_dump_free(void); +static void test_memory_listener_dump_free_null(void); +static void test_memory_listener_dump_error(void); +static void test_memory_listener_dump_error_null(void); + +/** + * memory_listner 単体テストスイート + */ +void suite_memory_listener(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_listener_allcate", test_memory_listener_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_free", test_memory_listener_free); + ut->add(ut, UT_TESTCASE, "memory_listener_error", test_memory_listener_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate", test_memory_listener_dump_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate(NULL)", test_memory_listener_dump_allocate_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free", test_memory_listener_dump_free); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free(NULL)", test_memory_listener_dump_free_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error", test_memory_listener_dump_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error(NULL)", test_memory_listener_dump_error_null); +} + +static char test_data[] = "ABCDEFG \x00\x81 0123456789"; +static KcMemoryEntry test_entry = { + .size = 14, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + +/** + * KcMemoryListner_allocate 動作確認 + * + * @process KcMemoryListener_allocate を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_allocate(void) +{ + KcMemoryListener_allocate(&test_entry); +} + +/** + * KcMemoryListner_free 動作確認 + * + * @process KcMemoryListener_free を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_free(void) +{ + KcMemoryListener_free(&test_entry); +} + +/** + * KcMemoryListner_error 動作確認 + * + * @process KcMemoryListener_error を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_error(void) +{ + KcMemoryListener_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_allocate 動作確認 + * + * @process KcMemoryListener_dump_allocate を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_allocate(void) +{ + KcMemoryListener_dump_allocate(&test_entry); +} + +/** + * KcMemoryListner_dump_allocate (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_allocate を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_allocate_null(void) +{ + KcMemoryListener_dump_allocate(NULL); +} + +/** + * KcMemoryListner_dump_free 動作確認 + * + * @process KcMemoryListener_dump_free を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_free(void) +{ + KcMemoryListener_dump_free(&test_entry); +} + +/** + * KcMemoryListner_dump_free (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_free を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_free_null(void) +{ + KcMemoryListener_dump_free(NULL); +} + +/** + * KcMemoryListner_dump_error 動作確認 + * + * @process KcMemoryListener_dump_error を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_error(void) +{ + KcMemoryListener_dump_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_error (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_error を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_error_null(void) +{ + KcMemoryListener_dump_error(NULL, "ERROR\n"); +} diff --git a/modules/test/src/test_memory_mark.c b/modules/test/src/test_memory_mark.c new file mode 100644 index 0000000..cd518f9 --- /dev/null +++ b/modules/test/src/test_memory_mark.c @@ -0,0 +1,87 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_mark_allocated(void); +static void test_memory_mark_allocated_new(void); +static void test_memory_mark_allocated_new_array(void); +static void test_memory_mark_deleted(void); +static void test_memory_mark_other(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_memory_mark(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED", test_memory_mark_allocated); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW", test_memory_mark_allocated_new); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW_ARRAY", test_memory_mark_allocated_new_array); + ut->add(ut, UT_TESTCASE, "memory_mark DELETED", test_memory_mark_deleted); + ut->add(ut, UT_TESTCASE, "memory_mark OTHER", test_memory_mark_other); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED) + * + * @process KC_MEMORY_ALLOCATED の文字列表現を取得する。 + * @result 'alloc ' が取得されること。 + */ +static void test_memory_mark_allocated(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED); + assert_equals("alloc ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW) + * + * @process KC_MEMORY_ALLOCATED_NEW の文字列表現を取得する。 + * @result 'new ' が取得されること。 + */ +static void test_memory_mark_allocated_new(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW); + assert_equals("new ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW_ARRAY) + * + * @process KC_MEMORY_ALLOCATED_NEW_ARRAY の文字列表現を取得する。 + * @result 'new[] ' が取得されること。 + */ +static void test_memory_mark_allocated_new_array(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW_ARRAY); + assert_equals("new[] ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_DELETED) + * + * @process KC_MEMORY_DELETED の文字列表現を取得する。 + * @result 'delete' が取得されること。 + */ +static void test_memory_mark_deleted(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_DELETED); + assert_equals("delete", res); +} + +/** + * KcMemoryMark の文字列表現取得 (その他) + * + * @process 規定外の値を指定し、 KcMemoryMark_to_string を実行する。 + * @result 'other ' が取得されること。 + */ +static void test_memory_mark_other(void) +{ + const char *res = KcMemoryMark_to_string(0x12345678); + assert_equals("other ", res); +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/modules/test/src/test_memory_entry.c b/modules/test/src/test_memory_entry.c new file mode 100644 index 0000000..e883e07 --- /dev/null +++ b/modules/test/src/test_memory_entry.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_entry_new(void); +static void test_memory_entry_new_alignment(void); +static void test_memory_entry_new_realloc(void); +static void test_memory_entry_set_null(void); + +/** + * memory_entry 単体テストスイート + */ +void suite_memory_entry(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_entry new/delete", test_memory_entry_new); + ut->add(ut, UT_TESTCASE, "memory_entry new (alignment)", test_memory_entry_new_alignment); + ut->add(ut, UT_TESTCASE, "memory_entry new (realloc)", test_memory_entry_new_realloc); + ut->add(ut, UT_TESTCASE, "memory_entry set (NULL Entry)", test_memory_entry_set_null); +} + +/** + * memory_entry 生成/破棄 + * + * @process KcMemoryEntry_new を用いて KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, 0, 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + + KcMemoryEntry_delete(entry); + // entry->mark には、KC_MEMORY_DELETED を格納してから破棄されているが、 + // free 後のメモリ領域は、確認不可。 +} + +/** + * memory_entry 生成 (alignment指定) + * + * @process alignment を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new_alignment(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + KcMemoryEntry_delete(entry); +} + +/** + * memory_entry 生成 (entry 指定) + * + * @process entry を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に更新されていること。 + */ +static void test_memory_entry_new_realloc(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + KcMemoryEntry *new_entry = KcMemoryEntry_new( + entry, 0, 250, KC_MEMORY_ALLOCATED, "test_file2", "test_func2", 123); + assert_not_null(new_entry); + assert_equals(KC_MEMORY_ALLOCATED, new_entry->mark); + assert_equals(250, new_entry->size); + assert_equals("test_file2", new_entry->file); + assert_equals("test_func2", new_entry->func); + assert_equals(123, new_entry->line); + + KcMemoryEntry_delete(new_entry); +} + +/** + * memory_entry 設定 + * + * @process NULL を指定して、KcMemoryEntry_set を用いて各値を設定する。 + * @result エントリが NULL のため、何も処理されないこと。 + */ +static void test_memory_entry_set_null(void) +{ + KcMemoryEntry_set(NULL, + 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 123); +} diff --git a/modules/test/src/test_memory_listener.c b/modules/test/src/test_memory_listener.c new file mode 100644 index 0000000..5927064 --- /dev/null +++ b/modules/test/src/test_memory_listener.c @@ -0,0 +1,146 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_listener_allocate(void); +static void test_memory_listener_free(void); +static void test_memory_listener_error(void); +static void test_memory_listener_dump_allocate(void); +static void test_memory_listener_dump_allocate_null(void); +static void test_memory_listener_dump_free(void); +static void test_memory_listener_dump_free_null(void); +static void test_memory_listener_dump_error(void); +static void test_memory_listener_dump_error_null(void); + +/** + * memory_listner 単体テストスイート + */ +void suite_memory_listener(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_listener_allcate", test_memory_listener_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_free", test_memory_listener_free); + ut->add(ut, UT_TESTCASE, "memory_listener_error", test_memory_listener_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate", test_memory_listener_dump_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate(NULL)", test_memory_listener_dump_allocate_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free", test_memory_listener_dump_free); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free(NULL)", test_memory_listener_dump_free_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error", test_memory_listener_dump_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error(NULL)", test_memory_listener_dump_error_null); +} + +static char test_data[] = "ABCDEFG \x00\x81 0123456789"; +static KcMemoryEntry test_entry = { + .size = 14, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + +/** + * KcMemoryListner_allocate 動作確認 + * + * @process KcMemoryListener_allocate を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_allocate(void) +{ + KcMemoryListener_allocate(&test_entry); +} + +/** + * KcMemoryListner_free 動作確認 + * + * @process KcMemoryListener_free を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_free(void) +{ + KcMemoryListener_free(&test_entry); +} + +/** + * KcMemoryListner_error 動作確認 + * + * @process KcMemoryListener_error を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_error(void) +{ + KcMemoryListener_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_allocate 動作確認 + * + * @process KcMemoryListener_dump_allocate を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_allocate(void) +{ + KcMemoryListener_dump_allocate(&test_entry); +} + +/** + * KcMemoryListner_dump_allocate (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_allocate を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_allocate_null(void) +{ + KcMemoryListener_dump_allocate(NULL); +} + +/** + * KcMemoryListner_dump_free 動作確認 + * + * @process KcMemoryListener_dump_free を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_free(void) +{ + KcMemoryListener_dump_free(&test_entry); +} + +/** + * KcMemoryListner_dump_free (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_free を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_free_null(void) +{ + KcMemoryListener_dump_free(NULL); +} + +/** + * KcMemoryListner_dump_error 動作確認 + * + * @process KcMemoryListener_dump_error を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_error(void) +{ + KcMemoryListener_dump_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_error (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_error を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_error_null(void) +{ + KcMemoryListener_dump_error(NULL, "ERROR\n"); +} diff --git a/modules/test/src/test_memory_mark.c b/modules/test/src/test_memory_mark.c new file mode 100644 index 0000000..cd518f9 --- /dev/null +++ b/modules/test/src/test_memory_mark.c @@ -0,0 +1,87 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_mark_allocated(void); +static void test_memory_mark_allocated_new(void); +static void test_memory_mark_allocated_new_array(void); +static void test_memory_mark_deleted(void); +static void test_memory_mark_other(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_memory_mark(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED", test_memory_mark_allocated); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW", test_memory_mark_allocated_new); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW_ARRAY", test_memory_mark_allocated_new_array); + ut->add(ut, UT_TESTCASE, "memory_mark DELETED", test_memory_mark_deleted); + ut->add(ut, UT_TESTCASE, "memory_mark OTHER", test_memory_mark_other); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED) + * + * @process KC_MEMORY_ALLOCATED の文字列表現を取得する。 + * @result 'alloc ' が取得されること。 + */ +static void test_memory_mark_allocated(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED); + assert_equals("alloc ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW) + * + * @process KC_MEMORY_ALLOCATED_NEW の文字列表現を取得する。 + * @result 'new ' が取得されること。 + */ +static void test_memory_mark_allocated_new(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW); + assert_equals("new ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW_ARRAY) + * + * @process KC_MEMORY_ALLOCATED_NEW_ARRAY の文字列表現を取得する。 + * @result 'new[] ' が取得されること。 + */ +static void test_memory_mark_allocated_new_array(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW_ARRAY); + assert_equals("new[] ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_DELETED) + * + * @process KC_MEMORY_DELETED の文字列表現を取得する。 + * @result 'delete' が取得されること。 + */ +static void test_memory_mark_deleted(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_DELETED); + assert_equals("delete", res); +} + +/** + * KcMemoryMark の文字列表現取得 (その他) + * + * @process 規定外の値を指定し、 KcMemoryMark_to_string を実行する。 + * @result 'other ' が取得されること。 + */ +static void test_memory_mark_other(void) +{ + const char *res = KcMemoryMark_to_string(0x12345678); + assert_equals("other ", res); +} diff --git a/modules/test/src/ut.c b/modules/test/src/ut.c new file mode 100644 index 0000000..41f9017 --- /dev/null +++ b/modules/test/src/ut.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +extern void suite_lock_guard(void); +extern void suite_memory_dump(void); +extern void suite_memory_entry(void); +extern void suite_memory_listener(void); +extern void suite_memory_mark(void); +extern void suite_list_array(void); + +int main(void) +{ + // UT Setup + // test_list(); + // test_memory(); + suite_lock_guard(); + suite_memory_dump(); + suite_memory_entry(); + suite_memory_listener(); + suite_memory_mark(); + suite_list_array(); + + KcMemory_start(false); + KcUt *ut = KcUt_get_instance(); + ut->run(ut); + + return 0; +} diff --git a/Makefile b/Makefile index 3cd0cdd..63b170d 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,10 @@ # TOPDIR ?= . RULEDIR ?= $(TOPDIR)/mk -NAME = libkc -TARGET = $(NAME).so -SUBDIRS = -USE_SO_VERSION = 0.0.1 +NAME = +TARGET = $(NAME) +SUBDIRS = modules +USE_SO_VERSION = # ------------------------------------------------------------------------------ # *-cmd.mk : コマンド diff --git a/lib/libkc.a b/lib/libkc.a new file mode 100644 index 0000000..4130e2e --- /dev/null +++ b/lib/libkc.a Binary files differ diff --git a/mk/all-rule.mk b/mk/all-rule.mk index c31bac6..f45e02f 100644 --- a/mk/all-rule.mk +++ b/mk/all-rule.mk @@ -19,4 +19,3 @@ endif endif endif - diff --git a/mk/base-auto.mk b/mk/base-auto.mk index fc9f523..ec0bc7b 100644 --- a/mk/base-auto.mk +++ b/mk/base-auto.mk @@ -16,6 +16,7 @@ OBJS = $(addprefix $(OBJDIR)/, $(notdir $(addsuffix .o, $(basename $(SRCS))))) DEPS = $(OBJS:$(OBJDIR)/%.o=$(OBJDIR)/%.d) + # ------------------------------------------------------------------------------ # LINK : リンカー設定 # C++ が含まれる場合、$(CXX) を使用する。 @@ -30,23 +31,22 @@ # ------------------------------------------------------------------------------ # ターゲットが ut.exe の場合の設定 # -# 1. NDEBUG, NDEBUG を常に有効に設定する。 +# 1. DEBUG を常に有効に設定する。 # 2. SRCDIR に、../src を追加する。 # 3. INCLUDES に、../include を追加する。 -# 4. DEFINE に -DUNITTEST -DNDEBUG -DDEBUG を追加する。 +# 4. DEFINE に -DUNITTEST -DDEBUG を追加する。 # ------------------------------------------------------------------------------ ifeq ($(strip $(TARGET)),ut.exe) DEBUG = 1 -NDEBUG = 1 SRCDIR += ../src INCLUDES += -I../include -DEFINE += -DUNITTEST -DNDEBUG -DDEBUG +DEFINE += -DUNITTEST -DDEBUG endif -ifeq ($(strip $(NDEBUG)),) +ifeq ($(strip $(DEBUG)),) # ------------------------------------------------------------------------------ -# NDEBUG が無効な場合の設定 +# DEBUG が無効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の OPTIMIZATION を有効にする # ------------------------------------------------------------------------------ CFLAGS += $(OPTIMIZATION) @@ -54,7 +54,7 @@ else # ------------------------------------------------------------------------------ -# NDEBUG が有効な場合の設定 +# DEBUG が有効な場合の設定 # CFLAGS, CXXFLAGS, LDFLAGS の DEBUG_OPTIONS を有効にする # ------------------------------------------------------------------------------ DEFINE += -DENABLED_MEMORY_MANAGE @@ -78,4 +78,3 @@ CXXFLAGS += $(CXX_WARNING_OPTIONS) CXXFLAGS += $(DEFINE) CXXFLAGS += $(DEPENDS_OPTIONS) - diff --git a/mk/base-conf.mk b/mk/base-conf.mk index 81c9e11..2cc417c 100644 --- a/mk/base-conf.mk +++ b/mk/base-conf.mk @@ -78,5 +78,3 @@ DEBUG_LDFLAGS += --coverage DEBUG_LDFLAGS += -g3 -ggdb - - diff --git a/mk/check-cppcheck-conf.mk b/mk/check-cppcheck-conf.mk index c94554a..7a1797d 100644 --- a/mk/check-cppcheck-conf.mk +++ b/mk/check-cppcheck-conf.mk @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------------ CPPCHECK = cppcheck CPPCHECK_FLAGS = --inconclusive --xml --xml-version=2 --enable=all -CPPHECK_REPORT_DIR = report/cppcheck +CPPCHECK_REPORT_DIR = report/cppcheck CPPCHECK_LOG = $(CPPCHECK_REPORT_DIR)/cppcheck.xml # ------------------------------------------------------------------------------ diff --git a/mk/check-cppcheck-rule.mk b/mk/check-cppcheck-rule.mk index a8420d3..ffe1c0c 100644 --- a/mk/check-cppcheck-rule.mk +++ b/mk/check-cppcheck-rule.mk @@ -8,8 +8,7 @@ done ifneq ($(strip $(TARGET)),) ifneq ($(strip $(TARGET)),ut.exe) - -@$(MKDIR) $(CPPCHECK_REPORT_DIR) + -@$(MKDIR) -p $(CPPCHECK_REPORT_DIR) $(CPPCHECK) $(CPPCHECK_FLAGS) $(CPPCHECK_SUPPRESS) $(INCLUDES) $(SRCDIR) 2> $(CPPCHECK_LOG) endif endif - diff --git a/mk/check-lcov-cobertura-conf.mk b/mk/check-lcov-cobertura-conf.mk index 3e27680..1a9a6e8 100644 --- a/mk/check-lcov-cobertura-conf.mk +++ b/mk/check-lcov-cobertura-conf.mk @@ -5,7 +5,6 @@ # ------------------------------------------------------------------------------ # 基本設定 # ------------------------------------------------------------------------------ -LCOV_COBERTURA = lcov_cobertura.py +LCOV_COBERTURA = lcov_cobertura LCOV_COBERTURA_REPORT_DIR = report/lcov LCOV_COBERTURA_INFO = $(LCOV_COBERTURA_REPORT_DIR)/lcov.info - diff --git a/mk/check-lcov-cobertura-rule.mk b/mk/check-lcov-cobertura-rule.mk index 110134d..1e95c5a 100644 --- a/mk/check-lcov-cobertura-rule.mk +++ b/mk/check-lcov-cobertura-rule.mk @@ -9,4 +9,3 @@ ifeq ($(strip $(TARGET)),ut.exe) $(LCOV_COBERTURA) $(LCOV_COBERTURA_INFO) -b `pwd` -o $(LCOV_COBERTURA_REPORT_DIR)/lcov-coverage.xml endif - diff --git a/mk/check-lcov-conf.mk b/mk/check-lcov-conf.mk index 014e20b..ad876ef 100644 --- a/mk/check-lcov-conf.mk +++ b/mk/check-lcov-conf.mk @@ -22,7 +22,4 @@ # ------------------------------------------------------------------------------ # CLEAN 時の削除ファイル追加 # ------------------------------------------------------------------------------ -ifeq ($(strip $(TARGET)),ut.exe) CLEAN_DIRS += $(LCOV_REPORT_DIR) -endif - diff --git a/mk/compile-c-rule.mk b/mk/compile-c-rule.mk index f523b94..afc8b00 100644 --- a/mk/compile-c-rule.mk +++ b/mk/compile-c-rule.mk @@ -5,3 +5,6 @@ -@$(MKDIR) -p $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(TESTOBJDIR)/%.o: %.c + -@$(MKDIR) -p $(TESTOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/mk/test-rule.mk b/mk/test-rule.mk index ba154eb..4bd8655 100644 --- a/mk/test-rule.mk +++ b/mk/test-rule.mk @@ -10,4 +10,3 @@ $(MAKE) ut.exe ./ut.exe endif - diff --git a/modules/Makefile b/modules/Makefile new file mode 100644 index 0000000..dca3d67 --- /dev/null +++ b/modules/Makefile @@ -0,0 +1,48 @@ +# ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= .. +RULEDIR ?= $(TOPDIR)/mk +NAME = libkc +TARGET = $(NAME) +SUBDIRS = test +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/include/kc.h b/modules/include/kc.h new file mode 100644 index 0000000..f35f2aa --- /dev/null +++ b/modules/include/kc.h @@ -0,0 +1,35 @@ +/** + * @file kc.h + * @brief Kantan C Library 共通ヘッダファイル。 + * @copyright 2001 - 2023 Nomura Kei + * @depends + * kc_windows.h + */ +#ifndef KC_H +#define KC_H + +/** + * 指定された変数が未使用であることを明示します。 + * @param val 未使用変数 + */ +#define UNUSED_VARIABLE(val) (void)(val) + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +// For C11 +#include +#include + +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +// For C++17 +#include +#include + +#else +// ============================================================================= +// C11, C++17 より古い場合は、ERROR +// ============================================================================= +#error "supports C11, C++17 or later" + +#endif +#include +#endif // KC_H diff --git a/modules/include/kc_assert.h b/modules/include/kc_assert.h new file mode 100644 index 0000000..03fc71d --- /dev/null +++ b/modules/include/kc_assert.h @@ -0,0 +1,71 @@ + +/** + * @file kc_assert.h + * @brief KC Assert モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ASSERT_H +#define KC_ASSERT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +#ifdef NDEBUG +// NDEBUG が定義されている場合は、assert と同様何もしない。 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) ((void)expected, (void)actual) +#define assert_equals_3(expected, actual, delta) ((void)expected, (void)actual, (void)delta) +#define assert_null(condition) ((void)condition) +#define assert_not_null(condition) ((void)condition) +#define assert_false(condition) ((void)condition) +#define assert_true(condition) ((void)condition) +#define assert_fail() ((void)0) + +#else +// DEBUG 有効 +#define assert_equals(...) KC_OVERLOAD(assert_equals_, __VA_ARGS__) +#define assert_equals_2(expected, actual) \ + _Generic((expected), \ + int: assert_equals_long_, \ + long: assert_equals_long_, \ + char *: assert_equals_string_)(expected, actual, __FILE__, __func__, __LINE__) + +#define assert_equals_3(expected, actual, delta) \ + _Generic((expected), \ + float: assert_equals_double_, \ + double: assert_equals_double_, \ + default: assert_equals_binary_)(expected, actual, delta, __FILE__, __func__, __LINE__) + +#define assert_null(condition) assert_null_(condition, __FILE__, __func__, __LINE__) +#define assert_not_null(condition) assert_not_null_(condition, __FILE__, __func__, __LINE__) +#define assert_false(condition) assert_false_(condition, __FILE__, __func__, __LINE__) +#define assert_true(condition) assert_true_(condition, __FILE__, __func__, __LINE__) +#define assert_fail() assert_fail_(__FILE__, __func__, __LINE__) +#endif // NDEBUG + + void assert_equals_long_(long expected, long actual, const char *file, const char *func, int line); + void assert_equals_double_(double expected, double actual, double delta, const char *file, const char *func, int line); + void assert_equals_string_(const char *expected, const char *actual, const char *file, const char *func, int line); + void assert_equals_binary_(const void *expected, const void *actual, size_t size, const char *file, const char *func, int line); + void assert_null_(const void *condition, const char *file, const char *func, int line); + void assert_not_null_(const void *condition, const char *file, const char *func, int line); + void assert_false_(bool condition, const char *file, const char *func, int line); + void assert_true_(bool condition, const char *file, const char *func, int line); + void assert_fail_(const char *file, const char *func, int line); + extern void (*assert_handler)(const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ASSERT_H diff --git a/modules/include/kc_iterator.h b/modules/include/kc_iterator.h new file mode 100644 index 0000000..d164fc1 --- /dev/null +++ b/modules/include/kc_iterator.h @@ -0,0 +1,63 @@ +/** + * @file kc_iterator.h + * @brief KC Iterator モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_ITERATOR_H +#define KC_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * Iterator オブジェクト + */ + typedef struct KcIterator_ + { + /** + * 次の要素の有無を取得します。 + * + * @param ite Iterator オブジェクト + * @return true/false (次の要素がある/次の要素がない) + */ + bool (*hasNext)(struct KcIterator_ *ite); + + /** + * 次の要素を取得します。 + * + * @param ite Iterator オブジェクト + * @param size 取得したオブジェクトのサイズ格納用 + * @return 次のオブジェクト + */ + const void *(*next)(struct KcIterator_ *ite, size_t *size); + + /** + * 次の要素を取得します。 + * 格納している扱う要素が文字列の場合にのみ利用可能です。 + * + * @param ite Iterator オブジェクト + * @return 次の要素(文字列) + */ + const char *(*nextString)(struct KcIterator_ *ite); + + /** 内部情報 */ + void *_info; + + } KcIterator; + + void KcIterator_delete(KcIterator *ite); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_ITERATOR_H diff --git a/modules/include/kc_list.h b/modules/include/kc_list.h new file mode 100644 index 0000000..5e279fb --- /dev/null +++ b/modules/include/kc_list.h @@ -0,0 +1,211 @@ +/** + * @file kc_list.h + * @brief List モジュールヘッダファイル + * @copyright 2002 - 2023 Nomura Kei + * @depends + * kc.h + * kc_macro.h + */ +#ifndef KC_LIST_H +#define KC_LIST_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 単一種類の要素を扱うことが可能なリスト。 + */ + typedef struct KcList_ + { + + /** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ + int (*size)(struct KcList_ *list); + + /** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ + bool (*is_empty)(struct KcList_ *list); + + /** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ + bool (*contains)(struct KcList_ *list, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ + bool (*add)(struct KcList_ *list, int index, const void *element, size_t size); + + /** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素のコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ + bool (*remove)(struct KcList_ *list, int index, void *element, size_t *size); + + /** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ + void (*sort)(struct KcList_ *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); + + /** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ + void (*clear)(struct KcList_ *list); + + /** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ + void *(*get)(struct KcList_ *list, int index, size_t *size); + + /** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ + bool (*set)(struct KcList_ *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); + + /** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ + int (*index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ + int (*last_index_of)(struct KcList_ *list, const void *element, size_t size); + + /** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ + KcIterator *(*iterator)(struct KcList_ *list, int index); + + /** + * リスト管理情報クリア用関数 + * + * @param list 対象リスト + */ + void (*cleanup_info)(struct KcList_ *list); + + /** + * リスト管理情報 + * 本オブジェクトはリスト実装者が利用するための情報へのポインタとなります。 + */ + void *_info; + + } KcList; + + /** + * LinkedList を構築します。 + * + * @return LinkedList + */ + KcList *KcList_new_LinkedList(void); + + /** + * サイズ固定の要素を管理する ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap 初期容量 + * @return ArrayList + */ + KcList *KcList_new_ArrayList(size_t size, int cap); + + /** + * KcList を破棄します。 + * + * @param list 破棄するリスト + */ + void KcList_delete(KcList *list); + +/** + * List を構築します。 + * 構築される List は、 Linked List となります。 + */ +#define KcList_new KcList_new_LinkedList + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_LIST_H diff --git a/modules/include/kc_lock_guard.h b/modules/include/kc_lock_guard.h new file mode 100644 index 0000000..8bb70bd --- /dev/null +++ b/modules/include/kc_lock_guard.h @@ -0,0 +1,58 @@ +/** + * @file kc_lock_guard.h + * @brief KC ロックガードモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#ifndef KC_LOCK_GUARD_H +#define KC_LOCK_GUARD_H + +#include +#include + + +/** + * LockGuard 管理構造体 + */ +typedef struct +{ + mtx_t* mutex; +} KcLockGuard; + + +/** + * 指定された lock (mtx_t のポインタ)を用い、指定された区間ロックします。 + * 使用例) + * + * mtx_t mutex; + * mtx_init(&mutex, mtx_plain | mtx_recursive); + * + * + * kc_lock_guard(&mutex) { + * // この区間ロックが取得されている状態 + * // ブロックを抜けるとロックが自動解除される。 + * // [注意] 本ブロック内では、break, return, goto 等を利用しないでください。 + * } + */ +#define kc_lock_guard(lock) \ + for (KcLockGuard _guard = kc_lock_guard_init(lock); _guard.mutex != NULL; kc_lock_guard_release(&_guard), _guard.mutex = NULL) + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex); + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard); + + +#endif // KC_LOCK_GUARD_H diff --git a/modules/include/kc_memory.h b/modules/include/kc_memory.h new file mode 100644 index 0000000..3ca9321 --- /dev/null +++ b/modules/include/kc_memory.h @@ -0,0 +1,245 @@ +/** + * @file kc_memory.h + * @brief KC メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * kc_memory_listener.h + * + * 利用例) + * #include + * int main(void) + * { + * // プログラム終了時に発生しているメモリリーク情報を出力します。 + * // 引数が true の場合、メモリ確保/解放情報を出力します。 + * KcMemory_start(true); + * ...(省略)... + * return 0; + * } + */ +#ifndef KC_MEMORY_H +#define KC_MEMORY_H + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +/** メモリ管理で扱うバッファサイズ */ +#define KC_MEMORY_MAX_BUFFER_SIZE (4096) + + /** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ + void KcMemory_start(bool detail); + + /** + * 現在確保去れているメモリ一覧を出力します。 + */ + void KcMemory_dump(void); + + /** + * メモリの確保、解放を管理します。 + */ + typedef struct KcMemoryManager_ + { + /** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * @code + * 例) + * void Listener_allocate(const KcMemoryEntry* entry) { + * printf("メモリ確保! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_free(const KcMemoryEntry* entry) { + * printf("メモリ解放! : %s:%d\n", entry->file, entry->line); + * } + * void Listener_error(const KcMemoryEntry* entry, const char* msg) { + * printf("エラー! : %s", msg); + * } + * + * int main (void) { + * KcMemoryListener listener = { + * .allocate = Listener_allocate, + * .free = Listener_free, + * .error = Listener_error + * }; + * kc_memory_manager->set_listener(&listener); + * ...(省略)... + * return 0; + * } + * @endcode + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ + bool (*set_listener)(KcMemoryListener *listener); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻りが false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ + bool (*entries)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ + bool (*freeif)(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); + + /** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param byte ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCIIがダンプデータに追加されます。 + * @param column カラム数 + */ + void (*dump)(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column); + + /** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*malloc)(size_t size, const char *file, const char *func, int line); + + /** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*aligned_alloc)(size_t alignement, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*calloc)(size_t nmemb, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ + void *(*realloc)(void *ptr, size_t size, const char *file, const char *func, int line); + + /** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ + void (*free)(void *ptr); + + // ========================================================================= + // 内部利用関数 + // ========================================================================= + + /** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 本関数は、KcMemoryManager の各関数が実行される際に呼び出され、 + * 一度だけ初期化処理を実施します。 + */ + void (*_init)(void); + + bool (*_add)(KcMemoryEntry *entry); + bool (*_remove)(KcMemoryEntry *entry); + void *(*_allocate)(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_managed_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_invalid_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void *(*_reallocate_unmanaged_ptr)(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + void (*_deallocate)(void *ptr, KcMemoryMark expected_mark); + + // ========================================================================= + // 内部利用変数 + // ========================================================================= + KcMemoryListener _listener; //!< リスナ + KcMemoryEntry _head; //!< 管理メモリの先頭 + KcMemoryEntry _tail; //!< 管理メモリの末尾 + KcMemoryEntry _error; //!< エラー発生時一時利用 + char _tmpbuf[KC_MEMORY_MAX_BUFFER_SIZE]; //!< 一時利用のためのバッファ + mtx_t *_mutex; //!< 同期実行利用のための Mutex + + } KcMemoryManager; + + /** + * KcMemoryManager の唯一のインスタンス。 + */ + extern KcMemoryManager *const kc_memory_manager; + +#ifdef KC_MEMORY_ENABLED +#define malloc(size) kc_memory_manager->malloc(size, __FILE__, __func__, __LINE__) +#define calloc(nmemb, size) kc_memory_manager->calloc(nmemb, size, __FILE__, __func__, __LINE__) +#define realloc(ptr, size) kc_memory_manager->realloc(ptr, size, __FILE__, __func__, __LINE__) +#define free(ptr) kc_memory_manager->free(ptr) +#else +#include +#endif + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_H diff --git a/modules/include/kc_memory_dump.h b/modules/include/kc_memory_dump.h new file mode 100644 index 0000000..6b84452 --- /dev/null +++ b/modules/include/kc_memory_dump.h @@ -0,0 +1,44 @@ +/** + * @file kc_memory_dump.h + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_DUMP_H +#define KC_MEMORY_DUMP_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 指定されたメモリエントリの情報を buff に出力します。 + * 常に指定された column の文字数となるように出力は調整されます。 + * buff_size < column の場合、出力に失敗し、false を返します。 + * + * @param buff 情報を出力するバッファ + * @param buff_size バッファサイズ + * @param entry メモリエントリ + * @param binary true の場合、データの16進数情報が出力に追加されます。 + * @param ascii true の場合、データのASCII 情報が出力に追加されます。 + * @param column 出力文字数 + * @return true/false (出力成功/出力失敗) + */ + bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, + int bytes, bool binary, bool ascii, int column); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_DUMP_H diff --git a/modules/include/kc_memory_entry.h b/modules/include/kc_memory_entry.h new file mode 100644 index 0000000..633b161 --- /dev/null +++ b/modules/include/kc_memory_entry.h @@ -0,0 +1,45 @@ +/** + * @file kc_memory_entry.h + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_ENTRY_H +#define KC_MEMORY_ENTRY_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリエントリ。 + */ + typedef struct KcMemoryEntry_ + { + int size; //!< 確保サイズ + KcMemoryMark mark; //!< 確保メモリ状態 + const char *file; //!< メモリ確保ファイル名 + const char *func; //!< メモリ確保関数名 + int line; //!< メモリ確保行番号 + struct KcMemoryEntry_ *_prev; //!< 前の管理メモリポインタ + struct KcMemoryEntry_ *_next; //!< 次の管理メモリポインタ + void *data; //!< データ + // 構造体末尾の配列に限りサイズ省略可能 (C99 : incomplete array) + } KcMemoryEntry; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_H diff --git a/modules/include/kc_memory_listener.h b/modules/include/kc_memory_listener.h new file mode 100644 index 0000000..a8b7899 --- /dev/null +++ b/modules/include/kc_memory_listener.h @@ -0,0 +1,59 @@ +/** + * @file kc_memory_listener.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_LISTENER_H +#define KC_MEMORY_LISTENER_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保、解放、エラー発生時の通知用リスナ。 + */ + typedef struct + { + /** + * メモリ確保の際に呼び出されます。 + * + * @param entry 確保されたメモリエントリ + */ + void (*allocate)(const KcMemoryEntry *entry); + + /** + * メモリ解放の際に呼び出されます。 + * + * @param entry 解放されるメモリエントリ + */ + void (*free)(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されます。 + * + * @param entry エラーが発生したメモリエントリ (NULL の場合があります。) + * @param msg エラー発生時のメッセージ + */ + void (*error)(const KcMemoryEntry *entry, const char *msg); + + } KcMemoryListener; + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_H diff --git a/modules/include/kc_memory_mark.h b/modules/include/kc_memory_mark.h new file mode 100644 index 0000000..cf62c68 --- /dev/null +++ b/modules/include/kc_memory_mark.h @@ -0,0 +1,52 @@ +/** + * @file kc_memory_mark.h + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * + * 本ヘッダファイルを直接 include せず、kc_memory.h を include してください。 + */ +#ifndef KC_MEMORY_MARK_H +#define KC_MEMORY_MARK_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ状態 + */ + typedef enum + { + KC_MEMORY_DELETED = 0x55AA0000, //!< 解放済み + KC_MEMORY_ALLOCATED = 0x55AA1111, //!< 確保済み + KC_MEMORY_ALLOCATED_NEW = 0x55AA2222, //!< new により確保済み + KC_MEMORY_ALLOCATED_NEW_ARRAY = 0x55AA4444 //!< new[] により確保済み + } KcMemoryMark; + + /** + * 指定されたメモリ状態に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ + const char *KcMemoryMark_to_string(int mark); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_MARK_H diff --git a/modules/include/kc_overload.h b/modules/include/kc_overload.h new file mode 100644 index 0000000..b175b32 --- /dev/null +++ b/modules/include/kc_overload.h @@ -0,0 +1,71 @@ +/** + * @file kc_overload.h + * @brief オーバーロード用マクロ。 + * @copyright 2022 - 2023 Nomura Kei + * @depends none + */ +#ifndef KC_OVERLOAD_H +#define KC_OVERLOAD_H + +/** + * 関数のオーバーロードを実現します。 + * 引数の数に応じて、「指定された関数名(のプレフィックス) + 引数の数」の関数が実行されます。 + * + * @code + * 例) add + * #define add(...) KC_OVERLOAD(add_, __VA_ARGS__) + * + * 引数が2つの場合、add_2 の関数が呼び出されます。 + * 引数が3つの場合、add_3 の関数が呼び出されます。 + * : + * 引数が9つの場合、add_9 の関数が呼び出されます。 + * + * + * @endcode + * + * @param func オーバーロードする関数のベース名 + * @param ... __VA_ARGS__ + * @return 引数に応じて呼び出す関数 + */ +#define KC_OVERLOAD(func, ...) KC_OVERLOAD_SUB(func, KC_ARGS_LENGTH(__VA_ARGS__))(__VA_ARGS__) + +//////////////////////////////////////////////////////////////////////////////// +// +// 以下、オーバーロード実現のためのマクロ群 +// + +/** + * 関数名を生成します。 + * 関数名ベース+引数の数の関数名を生成します。 + * + * 例) + * 関数名ベース add_ + * 引数の数 1 + * + * add_1 + * + * @param func 関数名のベース + * @param args_length 引数の数 + * @return 引数名 + */ +#define KC_OVERLOAD_SUB(func, args_length) KC_STRCAT(func, args_length) + +/** + * 可変長引数の個数を返します。 + * + * @param ... 可変長引数 + * @return 引数の数 + */ +#define KC_ARGS_LENGTH(...) KC_ARGS_LENGTH_SUB(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1) +#define KC_ARGS_LENGTH_SUB(v1, v2, v3, v4, v5, v6, v7, v8, v9, LENGTH, ...) LENGTH + +/** + * 指定された文字列を連結します。 + * + * @param s1 文字列1 + * @param s2 文字列2 + * @return 連結した文字列 + */ +#define KC_STRCAT(s1, s2) s1##s2 + +#endif // KC_OVERLOAD_H diff --git a/modules/include/kc_term.h b/modules/include/kc_term.h new file mode 100644 index 0000000..78cb6af --- /dev/null +++ b/modules/include/kc_term.h @@ -0,0 +1,82 @@ +/** + * @file kc_term.h + * @brief KC 端末制御モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_TERM_H +#define KC_TERM_H + +#include + +// ASCII エスケープコードによる表示 + +// 装飾 +#define KC_TERM_CLR "\x1b[0m" //!< 装飾無し +#define KC_TERM_BLD "\x1b[1m" //!< 太字 +#define KC_TERM_LGT "\x1b[2m" //!< 細字 +#define KC_TERM_ITA "\x1b[3m" //!< イタリック +#define KC_TERM_UND "\x1b[4m" //!< 下線 +#define KC_TERM_BLN "\x1b[5m" //!< 点滅 +#define KC_TERM_FBL "\x1b[6m" //!< 高速点滅 +#define KC_TERM_INV "\x1b[7m" //!< 反転表示 +#define KC_TERM_HID "\x1b[8m" //!< 非表示 (コピーは可能) +#define KC_TERM_CAN "\x1b[9m" //!< 取り消し + +// 文字色 +#define KC_TERM_DEF "\x1b[39m" //!< デフォルト +#define KC_TERM_BLK "\x1b[30m" //!< 黒 +#define KC_TERM_RED "\x1b[31m" //!< 赤 +#define KC_TERM_GRN "\x1b[32m" //!< 緑 +#define KC_TERM_YEL "\x1b[33m" //!< 黄 +#define KC_TERM_BLU "\x1b[34m" //!< 青 +#define KC_TERM_MAG "\x1b[35m" //!< 紫 +#define KC_TERM_CYN "\x1b[36m" //!< 水 +#define KC_TERM_WHT "\x1b[37m" //!< 白 +#define KC_TERM_H_BLK "\x1b[90m" //!< 黒(高輝度) +#define KC_TERM_H_RED "\x1b[91m" //!< 赤(高輝度) +#define KC_TERM_H_GRN "\x1b[92m" //!< 緑(高輝度) +#define KC_TERM_H_YEL "\x1b[93m" //!< 黄(高輝度) +#define KC_TERM_H_BLU "\x1b[94m" //!< 青(高輝度) +#define KC_TERM_H_MAG "\x1b[95m" //!< 紫(高輝度) +#define KC_TERM_H_CYN "\x1b[96m" //!< 水(高輝度) +#define KC_TERM_H_WHT "\x1b[97m" //!< 白(高輝度) +// 背景 +#define KC_TERM_BG_DEF "\x1b[49m" //!< デフォルト +#define KC_TERM_BG_BLK "\x1b[40m" //!< 黒 +#define KC_TERM_BG_RED "\x1b[41m" //!< 赤 +#define KC_TERM_BG_GRN "\x1b[42m" //!< 緑 +#define KC_TERM_BG_YEL "\x1b[43m" //!< 黄 +#define KC_TERM_BG_BLU "\x1b[44m" //!< 青 +#define KC_TERM_BG_MAG "\x1b[45m" //!< 紫 +#define KC_TERM_BG_CYN "\x1b[46m" //!< 水 +#define KC_TERM_BG_WHT "\x1b[47m" //!< 白 +#define KC_TERM_BG_H_BLK "\x1b[100m" //!< 黒(高輝度) +#define KC_TERM_BG_H_RED "\x1b[101m" //!< 赤(高輝度) +#define KC_TERM_BG_H_GRN "\x1b[102m" //!< 緑(高輝度) +#define KC_TERM_BG_H_YEL "\x1b[103m" //!< 黄(高輝度) +#define KC_TERM_BG_H_BLU "\x1b[104m" //!< 青(高輝度) +#define KC_TERM_BG_H_MAG "\x1b[105m" //!< 紫(高輝度) +#define KC_TERM_BG_H_CYN "\x1b[106m" //!< 水(高輝度) +#define KC_TERM_BG_H_WHT "\x1b[107m" //!< 白(高輝度) + +// 移動系 +#define KC_TERM_UP(n) "\x1b[" #n "A" //!< n 上に移動 +#define KC_TERM_DOWN(n) "\x1b[" #n "B" //!< n 下に移動 +#define KC_TERM_RIGHT(n) "\x1b[" #n "C" //!< n 右に移動 +#define KC_TERM_LEFT(n) "\x1b[" #n "D" //!< n 左に移動 +#define KC_TERM_DOWN_LINE(n) "\x1b[" #n "E" //!< n 行下に移動(行頭[1列目]) +#define KC_TERM_UP_LINE(n) "\x1b[" #n "F" //!< n 行上に移動(行頭[1列目]) +#define KC_TERM_COL(n) "\x1b[" #n "G" //!< n 列に移動 +#define KC_TERM_MOVE(n, m) "\x1b[" #n ";" #m "H" //!< n,m 位置へ移動 +#define KC_TERM_DEL_AFT "\x1b[0J" //!< カーソル以降を消去 +#define KC_TERM_DEL_BEF "\x1b[1J" //!< カーソル以前を消去 +#define KC_TERM_DEL "\x1b[2J" //!< 全体を消去 +#define KC_TERM_DEL_AFT_LINE "\x1b[0K" //!< カーソル行のカーソル以降を消去 +#define KC_TERM_DEL_BEF_LINE "\x1b[1K" //!< カーソル行のカーソル以前を消去 +#define KC_TERM_DEL_LINE "\x1b[2K" //!< カーソル行を消去 +#define KC_TERM_SCROLL(n) "\x1b[" #n "S" //!< n行分次にスクロール +#define KC_TERM_SCROLL_R(n) "\x1b[" #n "T" //!< n行分前にスクロール + +#endif // KC_TERM_H diff --git a/modules/include/kc_ut.h b/modules/include/kc_ut.h new file mode 100644 index 0000000..51e352c --- /dev/null +++ b/modules/include/kc_ut.h @@ -0,0 +1,83 @@ + +/** + * @file kc_ut.h + * @brief KC 単体テスト用モジュール + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + */ +#ifndef KC_UT_H +#define KC_UT_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * 関数種別。 + */ + typedef enum + { + UT_TESTCASE, //!< テストケース + UT_SETUP, //!< 事前処理 + UT_TEARDOWN, //!< 事後処理 + UT_OTHER //!< その他 + } UtType; + + /** + * UT実施用構造体 + * + * @code + * KcUt* ut = KcUt_get_instance(); + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リスト追加", UtList_add); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->add(ut, UT_SETUP, "事前処理", UtList_setup) + * ut->add(ut, UT_TESTCASE, "リストクリア", UtList_clear); + * ut->add(ut, UT_SETUP, "事後処理", UtList_teardown) + * + * ut->run(); + * @endcode + */ + typedef struct KcUt_ + { + /** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut 単体テスト用インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ + void (*add)(struct KcUt_ *ut, UtType type, const char *title, void (*func)(void)); + + /** + * テストケースを実行します。 + * + * @param ut 単体テスト用インスタンス + */ + void (*run)(struct KcUt_ *ut); + + /** + * 内部で扱うための情報格納用 + */ + void *_info; + } KcUt; + + /** + * 単体テスト用のインスタンスを生成します。 + */ + KcUt *KcUt_get_instance(void); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_UT_H diff --git a/modules/include/kc_windows.h b/modules/include/kc_windows.h new file mode 100644 index 0000000..ee70898 --- /dev/null +++ b/modules/include/kc_windows.h @@ -0,0 +1,49 @@ +/** + * @file kc_windows.h + * @brief KC Windows 用ヘッダファイル + * @copyright 2000 - 2023 Nomura Kei + * @depends none + * + * 本ヘッダーファイルでは、Windows の場合、よく利用されるヘッダファイルを + * インクルードし、必要な設定を実施する。 + */ +#ifndef KC_WINDOWS_H +#define KC_WINDOWS_H +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) || defined(__WIN32__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) || defined(__WIN64__) +#define KC_IS_WINDOWS (1) + +// DMC にて winsoc2.h を利用する場合、_WINSOCK_API_ が必要 +// 詳細は、下記URL参照 +// http://www.digitalmars.com/d/archives/c++/idde/326.html +#ifdef __DMC__ +#define _WINSOCKAPI_ +#include +#endif + +// サポートする OS バージョン指定として、Windows 10 以降を指定する。 +// 参考までに他バージョンの値は次の通り。 +// Windows 2000 0x05000 +// Windows XP 0x0501 +// Windows Server 2003 0x0502 +// Windows Server 2008 0x0600 +// Windows 7 0x0601 +// Windows 8 0x0602 +// Windows 10 0x0A00 +#ifndef WINVER +#define WINVER 0x0A00 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +// よく利用されるヘッダファイルをインクルードする +#include +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#else +#define KC_IS_WINDOWS (0) +#endif +#endif // KC_WINDOWS_H diff --git a/modules/src/kc_assert.c b/modules/src/kc_assert.c new file mode 100644 index 0000000..371fc8f --- /dev/null +++ b/modules/src/kc_assert.c @@ -0,0 +1,242 @@ +/** + * @file kc_assert.c + * @brief アサーションモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void assert_default_handler(const char *msg); +static const char *assert_hexdump(char *buf, const void *data, size_t size); + +// ハンドラ +/** + * Assertion 発生時に実行されるハンドラ。 + * + * @param msg エラーメッセージ + */ +void (*assert_handler)(const char *msg) = assert_default_handler; + +/** Assertion で利用するメッセージバッファ。 */ +static thread_local char assert_message[1024]; + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_long_( + long expected, long actual, const char *file, const char *func, int line) +{ + bool is_success = (expected == actual); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%ld> but was <%ld>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された値が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_double_( + double expected, double actual, double delta, const char *file, const char *func, int line) +{ + bool is_success = (fabs((double)expected - actual) < delta); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%f> but was <%f>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定された文字列が一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_string_( + const char *expected, const char *actual, const char *file, const char *func, int line) +{ + bool is_success = (strcmp(expected, actual) == 0); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected, actual); + assert_handler(assert_message); + } +} + +/** + * 指定されたバイナリが一致するか否か検査します。 + * + * @param expected 期待する値 + * @param actual 実際の値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_equals_binary_( + const void *expected, const void *actual, size_t size, const char *file, const char *func, int line) +{ + bool is_success = (memcmp(expected, actual, size) == 0); + if (!is_success) + { + char expected_buf[64]; + char actual_buf[64]; + const char *expected_dump = assert_hexdump(expected_buf, expected, size); + const char *actual_dump = assert_hexdump(actual_buf, actual, size); + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion expected <%s> but was <%s>", + file, line, func, expected_dump, actual_dump); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLであることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition == NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値がNULLでないことを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_not_null_(const void *condition, const char *file, const char *func, int line) +{ + bool is_success = (condition != NULL); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is null", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が false であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_false_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (!condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not false", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 指定された値が true であることを検査します。 + * + * @param condition 検査する値 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_true_(bool condition, const char *file, const char *func, int line) +{ + bool is_success = (condition); + if (!is_success) + { + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion condition is not true", file, line, func); + assert_handler(assert_message); + } +} + +/** + * 常に失敗であることを示します。 + * + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +void assert_fail_(const char *file, const char *func, int line) +{ + snprintf(assert_message, sizeof(assert_message), + "%s:%d: %s: Assertion fail()", file, line, func); + assert_handler(assert_message); +} + +/** + * デフォルトアサーションハンドラ。 + * + * @param is_success 成功か否か + * @param msg メッセージ + */ +static void assert_default_handler(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + abort(); +} + +/** + * 最大 16 バイトの16進数ダンプ文字列をバッファに格納します。 + * + * @param buff 結果を格納するためのバッファ [48 以上のこと] + * @param data データ + * @param size データのサイズ + */ +static const char *assert_hexdump(char *buf, const void *data, size_t size) +{ + int dump_size = (size < 16) ? size : 16; + const char *ptr = (const char *)data; + char *write_ptr = buf; + int write_size = 0; + for (int i = 0; i < dump_size; i++) + { + write_size += sprintf(write_ptr, "%02X ", *ptr); + write_ptr += write_size; + ptr++; + } + return buf; +} diff --git a/modules/src/kc_iterator.c b/modules/src/kc_iterator.c new file mode 100644 index 0000000..937d5d5 --- /dev/null +++ b/modules/src/kc_iterator.c @@ -0,0 +1,17 @@ +/** + * @file kc_iterator.c + * @brief イテレータモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * Iterator のメモリを解放します。 + * + * @param ite Iterator + */ +void KcIterator_delete(KcIterator *ite) +{ + free(ite); +} diff --git a/modules/src/kc_list.c b/modules/src/kc_list.c new file mode 100644 index 0000000..a59e643 --- /dev/null +++ b/modules/src/kc_list.c @@ -0,0 +1,21 @@ +/** + * @file kc_list.c + * @brief リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +/** + * List のメモリを解放します。 + * + * @param list List + */ +void KcList_delete(KcList *list) +{ + if (list->cleanup_info) + { + list->cleanup_info(list); + } + free(list); +} diff --git a/modules/src/kc_list_array.c b/modules/src/kc_list_array.c new file mode 100644 index 0000000..fd0beab --- /dev/null +++ b/modules/src/kc_list_array.c @@ -0,0 +1,663 @@ +/** + * @file kc_list_array.c + * @brief 配列リストモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#if defined(__GNUC__) +#define _GNU_SOURCE 1 +#define qsort_s qsort_r +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +/** + * KcArrayList 管理情報 + */ +typedef struct +{ + mtx_t mutex; //!< ロック用 + size_t element_size; //!< 要素のサイズ + int init_capacity; //!< 初期指定容量 + int capacity; //!< 現在の容量 + int size; //!< 現在の要素数 + int padding[2]; //!< パディング + void *data; //!< データ格納用バッファ +} KcArrayListInfo; + +// ============================================================================= +// プロトタイプ宣言 +// ============================================================================= +static int KcArrayList_size(KcList *list); +static bool KcArrayList_is_empty(KcList *list); +static bool KcArrayList_contains(KcList *list, const void *element, size_t size); +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size); +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size); +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args); +static void KcArrayList_clear(KcList *list); +static void *KcArrayList_get(KcList *list, int index, size_t *size); +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size); +static int KcArrayList_index_of(KcList *list, const void *element, size_t size); +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size); +static KcIterator *KcArrayList_iterator(KcList *list, int index); +static void KcArrayList_cleanup_info(KcList *list); + +static bool KcArrayList_increase_capacity(KcArrayListInfo *info); +static void KcArrayList_reduce_capacity(KcArrayListInfo *info); +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity); + +/** + * 指定されたサイズの要素を扱う ArrayList を構築します。 + * + * @param size 要素のサイズ + * @param cap リストの初期容量 + * @param file ファイル + * @param func 関数 + * @param line 行番号 + */ +KcList *KcList_new_ArrayList(size_t size, int cap) +{ + // KcArrayList の管理構造 + // +--------------+ + // | KcList | + // | ... | + // | _info -----------+ + // +--------------+ | + // | <_info> | <---+ + // | element_size | + // | capacity | + // | data ----------->+--------------+ + // +--------------+ | | + // | element[0] | + // | : | + KcList *list = (KcList *)malloc(sizeof(KcList) + sizeof(KcArrayListInfo)); + void *data = malloc(size * cap); + if ((list != NULL) && (data != NULL)) + { + list->size = KcArrayList_size; + list->is_empty = KcArrayList_is_empty; + list->contains = KcArrayList_contains; + list->add = KcArrayList_add; + list->remove = KcArrayList_remove; + list->sort = KcArrayList_sort; + list->clear = KcArrayList_clear; + list->get = KcArrayList_get; + list->set = KcArrayList_set; + list->index_of = KcArrayList_index_of; + list->last_index_of = KcArrayList_last_index_of; + list->iterator = KcArrayList_iterator; + list->cleanup_info = KcArrayList_cleanup_info; + list->_info = (list + 1); + + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + mtx_init(&(info->mutex), mtx_plain | mtx_recursive); + info->element_size = size; + info->init_capacity = cap; + info->capacity = cap; + info->size = 0; + info->data = data; + } + else + { // 何れかのメモリ確保に失敗したら、メモリを解放する。 + free(list); + list = NULL; + free(data); + data = NULL; + } + return list; +} + +// ----------------------------------------------------------------------------- +// size +// ----------------------------------------------------------------------------- +/** + * 対象リスト内にある要素の数を返します。 + * + * @param list 対象リスト + * @return 対象リスト内の要素数 + */ +static int KcArrayList_size(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + int size = -1; + kc_lock_guard(&(info->mutex)) + { + size = info->size; + } + return size; +} + +// ----------------------------------------------------------------------------- +// is_empty +// ----------------------------------------------------------------------------- +/** + * 対象リストに要素がない場合に true を返します。 + * + * @param list 対象リスト + * @return 対象リストに要素が含まれていない場合は true + */ +static bool KcArrayList_is_empty(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + bool is_empty = true; + kc_lock_guard(&(info->mutex)) + { + is_empty = (info->size == 0); + } + return is_empty; +} + +// ----------------------------------------------------------------------------- +// contains +// ----------------------------------------------------------------------------- +/** + * 指定の要素が対象リストに含まれている場合に true を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 対象リスト内にあるかどうか判定される要素 + * @param size 要素のサイズ + * @return 指定された要素が対象リスト内にある場合は true + */ +static bool KcArrayList_contains(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + bool is_contains = false; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + is_contains = true; + break; + } + } + } + return is_contains; +} + +// ----------------------------------------------------------------------------- +// add +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置に、指定された要素を挿入します。 + * index に負値を指定した場合、末尾に要素を追加します。 + * その位置の現在の要素(ある場合)とそれ以降の要素を右に移動します。(インデックスに1を加算)。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param index 指定の要素が挿入される位置のインデックス + * @param element 挿入される要素 + * @param size 要素のサイズ + * @return true/false (格納成功/失敗) + */ +static bool KcArrayList_add(KcList *list, int index, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + + int insert_index = (index < 0) ? info->size : index; + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= insert_index) && (insert_index <= info->size)); + is_success = is_success && KcArrayList_increase_capacity(info); + element_type *info_data = (element_type *)info->data; + if (is_success) + { + if (insert_index < info->size) + { // index 以降の要素を右に移動 + size_t n = (info->size - insert_index) * info->element_size; + memmove(&info_data[insert_index + 1], &info_data[insert_index], n); + } + + // データを追加 + memcpy(&info_data[insert_index], element, info->element_size); + info->size++; + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// remove +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を削除します。 + * 後続の要素を左に移動します(インデックスから1を減算)。 + * element が NULL でない場合、削除された要素のコピーが格納されます。 + * size が NULL でない場合、削除された要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 削除される要素のインデックス + * @param element 削除された要素をコピーが格納されます。 + * @param size 削除された要素のサイズが格納されます。 + * @return true/false (削除成功/失敗) + */ +static bool KcArrayList_remove(KcList *list, int index, void *element, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (element != NULL) + { + memcpy(element, &info_data[index], info->element_size); + } + if (size != NULL) + { + *size = info->element_size; + } + + if (index != (info->size - 1)) + { // index 以降の要素を左に移動 + size_t n = (info->size - (index + 1)) * info->element_size; + memmove(&info_data[index], &info_data[index + 1], n); + } + info->size--; + } + + // 容量削減 + KcArrayList_reduce_capacity(info); + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// sort +// ----------------------------------------------------------------------------- +/** + * [内部利用] + * ソート情報 + */ +typedef struct +{ + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args); + size_t element_size; + void *user_args; +} KcListSortInfo; + +/** + * [内部利用] + * KcArrayList_sort にて利用される、qsort_s に渡される comparator です。 + * + * @param x 比較する要素1 + * @param y 比較する要素2 + * @param context コンテキスト(KcListSortInfo) + * @return 比較結果 + */ +static int KcArrayList_comparator(const void *x, const void *y, void *context) +{ + KcListSortInfo *sort_info = (KcListSortInfo *)context; + int ret = sort_info->comparator(x, sort_info->element_size, y, sort_info->element_size, sort_info->user_args); + return ret; +} + +/** + * 指定された comparator が示す順序に従って、対象リストをソートします。 + * + * @param list 対象リスト + * @param comparator リスト要素を比較するために使用される comparator + * @param args comparator の第5引数に渡すオブジェクト + * @return true/false (ソート成功/ソート失敗) + */ +static void KcArrayList_sort(KcList *list, + int (*comparator)(const void *element1, size_t size1, + const void *element2, size_t size2, void *args), + void *args) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + kc_lock_guard(&(info->mutex)) + { + KcListSortInfo sort_info; + sort_info.comparator = comparator; + sort_info.element_size = info->element_size; + sort_info.user_args = args; + + qsort_s( + info_data, + info->size, + info->element_size, + KcArrayList_comparator, + &sort_info); + } +} + +// ----------------------------------------------------------------------------- +// clear +// ----------------------------------------------------------------------------- +/** + * すべての要素を対象リストから削除します。 + * + * @param list 対象リスト + */ +static void KcArrayList_clear(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + + kc_lock_guard(&(info->mutex)) + { + ((KcArrayListInfo *)list->_info)->size = 0; + KcArrayList_set_capacity(info, info->init_capacity); + } +} + +// ----------------------------------------------------------------------------- +// get +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を返します。 + * + * @param list 対象リスト + * @param index 取得する要素のインデックス + * @param size 要素のサイズを格納するポインタ + * @return 対象リスト内の指定された位置にある要素 + */ +static void *KcArrayList_get(KcList *list, int index, size_t *size) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + element_type *res = NULL; + kc_lock_guard(&(info->mutex)) + { + if ((0 <= index) && (index < info->size)) + { + res = &info_data[index]; + if (size != NULL) + { + *size = info->element_size; + } + } + } + return res; +} + +// ----------------------------------------------------------------------------- +// set +// ----------------------------------------------------------------------------- +/** + * 対象リスト内の指定された位置にある要素を、指定された要素に置き換えます。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * org_element が NULL でない場合、置き換え前の要素のコピーが格納されます。 + * org_size が NULL でない場合、置き換え前の要素のサイズが格納されます。 + * + * @param list 対象リスト + * @param index 置換される要素のインデックス + * @param element 指定された位置に格納される要素 + * @param size 指定された位置に格納される要素のサイズ + * @param org_element 指定された位置に以前あった要素のコピーが格納されます。 + * @param org_size 指定された位置に以前あった要素のサイズが格納されます。 + * @return true/false (置換成功/失敗) + */ +static bool KcArrayList_set(KcList *list, int index, const void *element, size_t size, + void *org_element, size_t *org_size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + bool is_success = true; + kc_lock_guard(&(info->mutex)) + { + is_success = ((0 <= index) && (index < info->size)); + if (is_success) + { + if (org_element != NULL) + { + memcpy(org_element, &info_data[index], info->element_size); + } + if (org_size != NULL) + { + *org_size = info->element_size; + } + memcpy(&info_data[index], element, info->element_size); + } + } + return is_success; +} + +// ----------------------------------------------------------------------------- +// index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最初に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最初に検出された位置のインデックス + */ +static int KcArrayList_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = 0; idx < info->size; idx++) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +// ----------------------------------------------------------------------------- +// last_index_of +// ----------------------------------------------------------------------------- +/** + * 指定された要素がこのリスト内で最後に検出された位置のインデックスを返します。 + * 指定された要素がこのリストにない場合は -1 を返します。 + * 要素サイズ固定のリストを扱う場合、size の値は無視されます。 + * + * @param list 対象リスト + * @param element 検索する要素 + * @param size 検索する要素のサイズ + * @return 指定された要素がこのリスト内で最後に検出された位置のインデックス + */ +static int KcArrayList_last_index_of(KcList *list, const void *element, size_t size) +{ + UNUSED_VARIABLE(size); + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + typedef uint8_t element_type[info->element_size]; + element_type *info_data = (element_type *)info->data; + + int res; + int result_index = -1; + kc_lock_guard(&(info->mutex)) + { + for (int idx = (info->size - 1); idx >= 0; idx--) + { + res = memcmp(&info_data[idx], element, info->element_size); + if (res == 0) + { + result_index = idx; + break; + } + } + } + return result_index; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Iterator +// + +/** + * ArrayListe の Iterator 管理用構造体 + */ +typedef struct KcArrayListIteratorEntry_ +{ + KcArrayListInfo *info; //!< ArrayList情報 + int current; //!< 現在のインデックス + int size; //!< リストのサイズ +} KcArrayListIteratorEntry; + +/** + * 次の要素があるか否かを返します。 + * + * @param ite Iterator + * @return true/false (次の要素が存在する/存在しない) + */ +static bool KcArrayListIterator_hasNext(KcIterator *ite) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + return (entry->current < entry->size); +} + +/** + * 次の要素を取得します。 + * size が指定されている場合、次の要素が size に格納されます。 + * 次の要素がない場合、NULL を返します。 + * + * @param ite Iterator + * @param size 要素のサイズ格納用 + * @return 次の要素 + */ +static const void *KcArrayListIterator_next(KcIterator *ite, size_t *size) +{ + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)ite->_info; + typedef uint8_t element_type[entry->info->element_size]; + element_type *info_data = (element_type *)entry->info->data; + + if (size != NULL) + { + *size = entry->info->element_size; + } + int tmp_index = entry->current; + entry->current++; + return info_data[tmp_index]; +} + +/** + * このリスト内の指定された位置で始まる、リスト内の要素を反復するイテレータを返します。 + * + * @param list 対象リスト + * @param index イテレータから返される最初の要素のインデックス + * @return リスト内の指定された位置で始まる、リスト内の要素を反復するイテレータ + */ +static KcIterator *KcArrayList_iterator(KcList *list, int index) +{ + size_t ite_size = sizeof(KcIterator) + sizeof(KcArrayListIteratorEntry); + KcIterator *ite = (KcIterator *)malloc(ite_size); + if (ite) + { + ite->hasNext = KcArrayListIterator_hasNext; + ite->next = KcArrayListIterator_next; + KcArrayListIteratorEntry *entry = (KcArrayListIteratorEntry *)(((KcIterator *)ite) + 1); + ite->_info = entry; + + entry->info = list->_info; + entry->current = index; + entry->size = list->size(list); + } + return ite; +} + +/** + * リスト情報をクリアします。 + * + * @param list 対象リスト + */ +static void KcArrayList_cleanup_info(KcList *list) +{ + KcArrayListInfo *info = (KcArrayListInfo *)list->_info; + free(info->data); +} + +/** + * 指定されたリスト情報のデータ容量を増やします。 + * 容量を増やす必要がない場合、何もせず true を返します。 + * 容量を増やすことができない場合、false を返します。 + * + * @param list リスト + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_increase_capacity(KcArrayListInfo *info) +{ + bool is_success = true; + if (info->size >= info->capacity) + { + int new_capacity = info->capacity * 2; + is_success = KcArrayList_set_capacity(info, new_capacity); + } + return is_success; +} + +/** + * 指定されたリスト情報のデータ容量を削減します。 + * + * @param list リスト + * @param info 配列リスト情報 + */ +static void KcArrayList_reduce_capacity(KcArrayListInfo *info) +{ + if ((info->capacity > info->init_capacity) && (info->size <= (info->capacity / 4))) + { // 初期容量より大きく、要素数が容量の1/4以下となった場合、容量を1/2に減らす。 + int new_capacity = info->capacity / 2; + KcArrayList_set_capacity(info, new_capacity); + } +} + +/** + * 指定されたリスト情報のデータ容量を指定された capacity に変更します。 + * + * @param info 配列リスト情報 + * @return true/false (成功/失敗) + */ +static bool KcArrayList_set_capacity(KcArrayListInfo *info, int capacity) +{ + // printf("p = %p\n", info->data); + // printf("size = %ld\n", info->element_size * capacity); + void *ptr = realloc(info->data, (info->element_size * capacity)); + if (ptr != NULL) + { + info->data = ptr; + info->capacity = capacity; + return true; + } + return false; +} diff --git a/modules/src/kc_lock_guard.c b/modules/src/kc_lock_guard.c new file mode 100644 index 0000000..6ef68e5 --- /dev/null +++ b/modules/src/kc_lock_guard.c @@ -0,0 +1,50 @@ +/** + * @file kc_lock_guard.c + * @brief ロックガードモジュール + */ +#include +#include + +#include + + +/** + * 指定された mutex がロックされるまで現在のスレッドをロックします。 + * ロックに成功するとロック管理用のオブジェクトを返します。 + * + * @param mutex mutex + * @return ロック管理用オブジェクト + */ +KcLockGuard kc_lock_guard_init(mtx_t* mutex) +{ + KcLockGuard guard = { .mutex = mutex }; + if (mutex == NULL) + { + errno = EINVAL; + perror("kc_lock_guard_init: (mutex = NULL)"); + return guard; + } + + int ret = mtx_lock(mutex); + if (ret != thrd_success) + { + perror("kc_lock_guard : mtx_lock error"); + guard.mutex = NULL; + } + return guard; +} + + +/** + * 指定されたロック管理用オブジェクトのロックを解除します。 + * + * @param guard ロック管理用オブジェクト + */ +void kc_lock_guard_release(KcLockGuard* guard) +{ + if (guard->mutex != NULL) { + mtx_unlock(guard->mutex); + guard->mutex = NULL; + } +} + diff --git a/modules/src/kc_memory.c b/modules/src/kc_memory.c new file mode 100644 index 0000000..3b807c0 --- /dev/null +++ b/modules/src/kc_memory.c @@ -0,0 +1,712 @@ +/** + * @file kc_memory.c + * @brief メモリ管理モジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include +#include +#include + +// 常に本来の malloc, free を利用するため、KC_MEMORY_ENABLED を無効化する。 +#ifdef KC_MEMORY_ENABLED +#undef KC_MEMORY_ENABLED +#endif +#include +#include +#include "kc_memory_entry_inner.h" +#include "kc_memory_listener_inner.h" + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// プロトタイプ宣言 + +// --- KcMemory +static void KcMemory_dump_leak(void); +static bool KcMemory_print(const char *msg); + +// --- KcMemoryManager +static bool KcMemoryManager_set_listener(KcMemoryListener *listener); +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info); +static void KcMemoryManager_dump(bool (*handler)(const char *data), int bytes, bool binary, bool ascii, int max_column); +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line); +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line); +static void KcMemoryManager_free(void *ptr); +static void KcMemoryManager_init(void); +static void KcMemoryManager_init_nop(void); +static bool KcMemoryManager_add(KcMemoryEntry *entry); +static bool KcMemoryManager_remove(KcMemoryEntry *entry); +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark); + +//////////////////////////////////////////////////////////////////////////////// +// +// 変数 +// + +// --- 公開変数 +static mtx_t KcMemoryManager_mutex; +static KcMemoryManager KcMemoryManager_mmgr = { + + // --- 公開関数 --- + .set_listener = KcMemoryManager_set_listener, + .entries = KcMemoryManager_entries, + .freeif = KcMemoryManager_freeif, + .dump = KcMemoryManager_dump, + .malloc = KcMemoryManager_malloc, + .aligned_alloc = KcMemoryManager_aligned_alloc, + .calloc = KcMemoryManager_calloc, + .realloc = KcMemoryManager_realloc, + .free = KcMemoryManager_free, + + // --- 内部関数 --- + ._init = KcMemoryManager_init, + ._add = KcMemoryManager_add, + ._remove = KcMemoryManager_remove, + ._allocate = KcMemoryManager_allocate, + ._reallocate = KcMemoryManager_reallocate, + ._reallocate_managed_ptr = KcMemoryManager_reallocate_managed_ptr, + ._reallocate_invalid_ptr = KcMemoryManager_reallocate_invalid_ptr, + ._reallocate_unmanaged_ptr = KcMemoryManager_reallocate_unmanaged_ptr, + ._deallocate = KcMemoryManager_deallocate, + + // --- 変数 --- + ._listener = { + .allocate = KcMemoryListener_allocate, + .free = KcMemoryListener_free, + .error = KcMemoryListener_error}, + ._head = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._tail, ._next = &KcMemoryManager_mmgr._tail, .data = NULL}, + ._tail = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = &KcMemoryManager_mmgr._head, ._next = &KcMemoryManager_mmgr._head, .data = NULL}, + ._error = {.size = 0, .mark = KC_MEMORY_DELETED, .file = NULL, .func = NULL, .line = 0, ._prev = NULL, ._next = NULL, .data = NULL}, + ._tmpbuf = {0}, + ._mutex = &KcMemoryManager_mutex}; + +KcMemoryManager *const kc_memory_manager = &KcMemoryManager_mmgr; + +// ============================================================================= +// KcMemory +// ============================================================================= +static KcMemoryListener KcMemory_listener; + +/** + * メモリ管理を開始します。 + * + * 本関数を実行することで、次の動作が実施されます。 + * (1) メモリ確保失敗時に、エラー情報を出力します。 + * (2) プログラム終了時に、解放されていないメモリを出力します。 + * (3) detail が true の場合、メモリ確保/解放時に出力します。 + * ※出力先は、いずれも stderr となります。 + * + * @param detail true/false (メモリ確保/解放の情報を出力する/出力しない) + */ +void KcMemory_start(bool detail) +{ + kc_memory_manager->_init(); + atexit(KcMemory_dump_leak); + if (detail) + { + KcMemory_listener.allocate = KcMemoryListener_dump_allocate; + KcMemory_listener.free = KcMemoryListener_dump_free; + } + else + { + KcMemory_listener.allocate = NULL; + KcMemory_listener.free = NULL; + } + KcMemory_listener.error = KcMemoryListener_dump_error; + kc_memory_manager->set_listener(&KcMemory_listener); +} + +/** + * 現在確保されているメモリ情報を出力します。 + */ +void KcMemory_dump(void) +{ + kc_memory_manager->dump(KcMemory_print, 16, true, true, 130); +} + +/** + * メモリリークしているメモリ情報をダンプします。 + */ +static void KcMemory_dump_leak(void) +{ + if (kc_memory_manager->_head._next != &(kc_memory_manager->_tail)) + { + fprintf(stderr, "##### Leak Memory #####\n"); + KcMemory_dump(); + } +} + +/** + * 指定されたメッセージを標準エラーに出力し、true を返します。 + * + * @param msg 標準エラーに出力するメッセージ + * @return true (固定) + */ +static bool KcMemory_print(const char *msg) +{ + fprintf(stderr, "%s", msg); + return true; +} + +// ============================================================================= +// KcMemoryManager +// ============================================================================= + +// ------------------------------------- +// set_listener +// ------------------------------------- +/** + * メモリ確保、解放、エラー発生時に通知するためのリスナを登録します。 + * + * @param listener 登録するリスナ + * @return true/false (リスナ登録成功/リスナ登録失敗) + */ +static bool KcMemoryManager_set_listener(KcMemoryListener *listener) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { // リスナ関数を設定。 + // 関数が NULL の場合は、デフォルトの関数を設定する。 + kc_memory_manager->_listener.allocate = (listener->allocate != NULL) + ? listener->allocate + : KcMemoryListener_allocate; + kc_memory_manager->_listener.free = (listener->free != NULL) + ? listener->free + : KcMemoryListener_free; + kc_memory_manager->_listener.error = (listener->error != NULL) + ? listener->error + : KcMemoryListener_error; + } + return true; +} + +// ------------------------------------- +// entries +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が false の場合、呼び出しを終了します。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true/false (実行成功/実行失敗) + */ +static bool KcMemoryManager_entries(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = handler(current, info); + } + } + return true; +} + +// ------------------------------------- +// freeif +// ------------------------------------- +/** + * 管理しているメモリエントリを引数に指定されたハンドラを実行します。 + * ハンドラの戻り値が true の場合、該当するメモリを解放します。 + * new で確保されたメモリが解放される際、デストラクタは呼び出されないため注意が必要です。 + * + * @param handler ハンドラ + * @param info ハンドラの第二引数に渡される付加情報 + * @return true 固定 + */ +static bool KcMemoryManager_freeif(bool (*handler)(const KcMemoryEntry *entry, void *info), void *info) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_free = false; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + current != &(kc_memory_manager->_tail); + /* NOP */ + ) + { + is_free = handler(current, info); + current = current->_next; + if (is_free) + { + kc_memory_manager->free(current->_prev->data); + } + } + } + return true; +} + +// ------------------------------------- +// dump +// ------------------------------------- +/** + * 管理しているメモリ情報のダンプデータを引数に指定されたハンドラを実行します。 + * + * @param handler ハンドラ + * @param bytes ダンプするバイト数 + * @param binary true の場合、バイナリの16進数表現がダンプデータに追加されます。 + * @param ascii true の場合、ASCII がダンプデータに追加されます。 + * @param column カラム数 + */ +static void KcMemoryManager_dump(bool (*handler)(const char *data), + int bytes, bool binary, bool ascii, int column) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + bool is_continue = true; + for ( + KcMemoryEntry *current = kc_memory_manager->_head._next; + is_continue && (current != &(kc_memory_manager->_tail)); + current = current->_next) + { + is_continue = KcMemoryDump_dump( + kc_memory_manager->_tmpbuf, + KC_MEMORY_MAX_BUFFER_SIZE, + current, + bytes, + binary, + ascii, + column); + if (is_continue) + { // エラーでなければハンドラを実行する。 + is_continue = handler(kc_memory_manager->_tmpbuf); + } + } + } +} + +/** + * 指定されたサイズのメモリを確保します。 + * + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @psram func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_malloc(size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(0, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * アライメント指定付きで、指定されたサイズのメモリを確保します。 + * + * @param alignemnt アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_aligned_alloc(size_t alignment, size_t size, const char *file, const char *func, int line) +{ + void *ptr = kc_memory_manager->_allocate(alignment, size, KC_MEMORY_ALLOCATED, file, func, line); + return ptr; +} + +/** + * 指定されたサイズの nmemb 個の要素からなるメモリを確保します。 + * 確保したメモリ領域の内容は、0x00 で初期化されます。 + * + * @param nmemb 確保するメモリ要素数 + * @param size 1要素あたりのメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_calloc(size_t nmemb, size_t size, const char *file, const char *func, int line) +{ + size_t n = nmemb * size; + void *ptr = kc_memory_manager->_allocate(size, n, KC_MEMORY_ALLOCATED, file, func, line); + if (ptr != NULL) + { + memset(ptr, 0x00, n); + } + return ptr; +} + +/** + * 指定されたポインタが指すメモリサイズを変更します。 + * + * @param ptr メモリサイズを変更するポインタ + * @param size 変更後のメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_realloc(void *ptr, size_t size, const char *file, const char *func, int line) +{ + void *new_ptr = kc_memory_manager->_reallocate(ptr, size, KC_MEMORY_ALLOCATED, file, func, line); + return new_ptr; +} + +/** + * 指定されたメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + */ +static void KcMemoryManager_free(void *ptr) +{ // malloc, calloc 等で確保されたメモリを解放する。 + kc_memory_manager->_deallocate(ptr, KC_MEMORY_ALLOCATED); +} + +// ------------------------------------- +// _init (初回目呼出し) +// ------------------------------------- +/** + * [内部利用関数] + * KcMemoryManager を初期化します。 + * 内部で利用する mutex を初期化します。 + */ +static void KcMemoryManager_init(void) +{ // _init に初期化ダミー関数を設定し、 + // 2回目以降本関数が Call されないようにする。 + kc_memory_manager->_init = KcMemoryManager_init_nop; + + // mutex を初期化する。 + int result = mtx_init(kc_memory_manager->_mutex, mtx_plain | mtx_recursive); + if (result != thrd_success) + { // 基本的に失敗しないが、失敗した場合は、mutex に NULL を設定する。 + perror("kc_memory : can't init mutex"); + kc_memory_manager->_mutex = NULL; + } +} + +// ------------------------------------- +// _init (2回目呼び出し以降) +// ------------------------------------- +/** + * KcMemoryManager の初期化ダミー関数。 + * _init の2回目以降の実行は、本関数が Call されます。 + */ +static void KcMemoryManager_init_nop(void) +{ + // NOP +} + +// ------------------------------------- +// _add +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理に追加します。 + * + * @param entry 追加するメモリエントリ + * @return true/false (追加実施/追加失敗) + */ +static bool KcMemoryManager_add(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // [tail] の 1つ前に挿入する + entry->_next = &(kc_memory_manager->_tail); + entry->_prev = kc_memory_manager->_tail._prev; + kc_memory_manager->_tail._prev->_next = entry; + kc_memory_manager->_tail._prev = entry; + } + return true; +} + +// ------------------------------------- +// _remove +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたメモリエントリをメモリ管理より削除します。 + * + * @param entry 削除するメモリエントリ + * @return true/false (削除実施/削除失敗) + */ +static bool KcMemoryManager_remove(KcMemoryEntry *entry) +{ + kc_memory_manager->_init(); + kc_lock_guard(kc_memory_manager->_mutex) + { + // entry の前後を直接リンクさせる + entry->_prev->_next = entry->_next; + entry->_next->_prev = entry->_prev; + } + return true; +} + +// ------------------------------------- +// _allocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたサイズのメモリを確保します。 + * 確保に失敗した場合、NULL を返します。 + * alignment > 0 の場合、アライメント指定でメモリを確保します。 + * + * @param alignment アライメント + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_allocate(size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + void *data_ptr = NULL; + KcMemoryEntry *entry = KcMemoryEntry_new(NULL, alignment, size, mark, file, func, line); + if (entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_add(entry); + kc_memory_manager->_listener.allocate(entry); + data_ptr = entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't allocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定された ptr のメモリサイズを変更します。 + * ptr = NULL の場合は、KcMemoryManager_allocate の alignemt = 0 と同様の動作となります。 + * 確保に失敗した場合、NULL を返します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (ptr == NULL) + { + return kc_memory_manager->_allocate(0, size, mark, file, func, line); + } + + void *data_ptr = NULL; + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ => 通常の allocate + data_ptr = kc_memory_manager->_allocate(0, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED: // 管理されたメモリの realloc + data_ptr = kc_memory_manager->_reallocate_managed_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリの realloc のためエラー + data_ptr = kc_memory_manager->_reallocate_invalid_ptr(ptr, size, mark, file, func, line); + break; + default: // 管理外メモリの realloc + data_ptr = kc_memory_manager->_reallocate_unmanaged_ptr(ptr, size, mark, file, func, line); + break; + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_managed_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理されたメモリ領域に対する realloc を実施します。 + * ※指定するポインタは、必ず管理されたメモリ領域である必要があります。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_managed_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + + // (A) 一旦メモリを管理から外す。 + kc_memory_manager->_remove(entry); + + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(entry, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + + // (B) 古いメモリ領域は残っているため、(A) のメモリを管理対象に戻す。 + kc_memory_manager->_add(entry); + } + return data_ptr; +} + +// ------------------------------------- +// _reallocate_invalid_ptr +// ------------------------------------- +/** + * [内部利用関数] + * new, new[] で確保されたメモリに対する realloc 実施によるエラー処理を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_invalid_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + UNUSED_VARIABLE(ptr); + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate (invalid pointer)\n"); + errno = EINVAL; + return NULL; +} + +// ------------------------------------- +// _reallocate_unmanaged_ptr +// ------------------------------------- +/** + * [内部利用関数] + * 管理外メモリ領域に対する realloc を実施します。 + * + * @param ptr ポインタ + * @param size 確保するメモリサイズ + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 確保したメモリへのポインタ + */ +static void *KcMemoryManager_reallocate_unmanaged_ptr(void *ptr, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + // | + // +----------+-------------------+ + // | 元の領域 | 追加分 + 管理領域 | + // +----------+-------------------+ + // ↓ + // ↓ memmove で 元の領域 + 追加分を、 + // ↓ 管理領域分を確保した先にコピーする + // ↓ + // +----------+----------+--------+ + // | 管理領域 | 元の領域 | 追加分 | + // +----------+----------+--------+ + void *data_ptr = NULL; + KcMemoryEntry *new_entry = KcMemoryEntry_new(ptr, 0, size, mark, file, func, line); + if (new_entry != NULL) + { // メモリ確保成功 + // memmove で 元の領域 + 追加分 をコピーして、メモリエントリとして追加する。 + memmove((new_entry + 1), new_entry, size); + kc_memory_manager->_add(new_entry); + kc_memory_manager->_listener.allocate(new_entry); + data_ptr = new_entry->data; + } + else + { // メモリ確保失敗 => エラー通知する。 + KcMemoryEntry_set(&(kc_memory_manager->_error), size, mark, file, func, line); + kc_memory_manager->_listener.error(&kc_memory_manager->_error, "can't reallocate\n"); + } + return data_ptr; +} + +// ------------------------------------- +// _deallocate +// ------------------------------------- +/** + * [内部利用関数] + * 指定されたポインタのメモリを解放します。 + * + * @param ptr 解放するメモリへのポインタ + * @param expected_mark 期待するメモリ状態 + */ +static void KcMemoryManager_deallocate(void *ptr, KcMemoryMark expected_mark) +{ + if (ptr == NULL) + { + return; + } + + KcMemoryEntry *entry = (KcMemoryEntry *)ptr; + entry--; + if (entry->mark == expected_mark) + { // 期待するメモリ状態の場合、そのまま解放する。 + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + } + else + { // 期待通りでない場合、メモリ状態に応じて警告を通知する。 + switch (entry->mark) + { + case KC_MEMORY_DELETED: // 削除済みメモリ + // Nothing to do. + break; + case KC_MEMORY_ALLOCATED: // malloc 等で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use free)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW: // new で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete)\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: // new[] で確保されたメモリ + kc_memory_manager->_listener.error(entry, "invalid free memory (please use delete[])\n"); + kc_memory_manager->_listener.free(entry); + kc_memory_manager->_remove(entry); + KcMemoryEntry_delete(entry); + break; + default: // 管理外メモリ + free(ptr); + break; + } + } +} diff --git a/modules/src/kc_memory_dump.c b/modules/src/kc_memory_dump.c new file mode 100644 index 0000000..111e328 --- /dev/null +++ b/modules/src/kc_memory_dump.c @@ -0,0 +1,326 @@ +/** + * @file kc_memory_dump.c + * @brief KC メモリダンプモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// +// 構造体 +// + +/** + * バッファ情報構造体 + */ +typedef struct +{ + char *write_ptr; // 考慮 + if (((int)buff_size + 2) < column) + { // バッファ不足の場合は何もしない + return false; + } + + KcMemoryDumpBufferInfo buff_info; + buff_info.write_ptr = buff; + buff_info.rest_size = buff_size; + + // ファイル名:行番号 (size bytes) [関数名] を書き込み + int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column); + if (info_column <= 0) + { + KcMemoryDump_dump_info(&buff_info, entry, column); + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + return true; + } + + KcMemoryDump_dump_info(&buff_info, entry, info_column); + + if (binary) + { // 16進数ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_binary(&buff_info, entry, bytes); + } + + if (ascii) + { // ASCII ダンプ + KcMemoryDump_dump_message(&buff_info, " | "); + KcMemoryDump_dump_ascii(&buff_info, entry, bytes); + } + + // 改行追加 + int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n"); + buff_info.write_ptr += write_size; + buff_info.rest_size -= write_size; + + return true; +} + +/** + * メモリ情報を表示するカラム数を算出します。 + * @code + * ファイル名:行番号 (size bytes) [関数名] + * @codeend + * + * @param info バッファ情報 + * @param bytes ダンプするバイト数 + * @param binary バイナリの16進数情報を出力するか否か + * @param ascii ASCIIを出力するか否か + * @param max_column 最大桁数 + * @return メモリ情報を表示する部分のカラム数 + */ +static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo *info, + int bytes, bool binary, bool ascii, int max_column) +{ + int info_column = (max_column < (info->rest_size - 1)) + ? max_column + : (info->rest_size - 1); + + info_column -= (binary) ? (bytes * 3) + 3 : 0; + info_column -= (ascii) ? (bytes) + 3 : 0; + + return info_column; +} + +/** + * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。 + * info の rest_size が 0 未満の場合は、何もしません。 + * info の rest_size を超えるメッセージは書き込まれません。 + * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * @param info バッファ情報 + * @param msg 書き込むメッセージ + */ +static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo *info, const char *msg) +{ + if (info->rest_size <= 0) + { // 空きバッファサイズ無しのため、何もしない。 + return; + } + int write_size = snprintf(info->write_ptr, info->rest_size, msg); + if (write_size > info->rest_size) + { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + write_size = (info->rest_size - 1); + } + info->write_ptr += write_size; + info->rest_size -= write_size; +} + +/** + * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。 + * 書き込む情報の文字数は、column に指定された文字数揃えられます。 + * ※空白でパディングされます。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param column 制限文字数 + */ +static void KcMemoryDump_dump_info( + KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int column) +{ + // 事前チェックされるため下記チェックは不要 + // if (info->rest_size <= 0) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + char size_buff[16]; + KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size); + int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [%s]", + entry->file, entry->line, size_buff, entry->func); + int padding = column - write_size; + // 事前チェックされるため下記チェックは不要 + // if (write_size > info->rest_size) + // { // msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く) + // write_size = (info->rest_size - 1); + // } + + if (write_size > column) + { // 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。 + write_size = column; + } + + info->write_ptr += write_size; + info->rest_size -= write_size; + *(info->write_ptr) = '\0'; + + if (padding > 0) + { + // 事前にチェック済みのため、padding < info->rest_size となる。 + // padding = (padding < info->rest_size) ? padding : (info->rest_size - 1); + memset(info->write_ptr, ' ', padding); + info->write_ptr += padding; + info->rest_size -= padding; + *(info->write_ptr) = '\0'; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes * 3; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "-- "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。 + * バッファサイズが不足する場合は、何も出力しません。 + * + * @param info バッファ情報 + * @param entry メモリエントリ + * @param bytes ダンプするバイト数 + */ +static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes) +{ + // 事前チェックされるため下記チェックは不要 + // int required_size = bytes; + // if (info->rest_size < required_size) + // { // 空きバッファサイズ無しのため、何もしない。 + // return; + // } + + const unsigned char *data_ptr = (const unsigned char *)entry->data; + int data_len = ((int)entry->size < bytes) ? (int)entry->size : bytes; + + int idx = 0; + for (; idx < data_len; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx])); + info->write_ptr += write_size; + info->rest_size -= write_size; + } + for (; idx < bytes; idx++) + { + int write_size = snprintf(info->write_ptr, info->rest_size, " "); + info->write_ptr += write_size; + info->rest_size -= write_size; + } +} + +/** + * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。 + * バッファのサイズは、12 Byte 以上である必要があります。 + * + * @param buff バッファ + * @param size サイズ + */ +static void KcMemoryDump_format_size(char *buff, size_t buff_size, size_t size) +{ + // UINT64_MAX ~ 16EB, ... PB, EB, ZB, YB, RB, QB + static const char *SIZE_UNIT[] = {" B", "KB", "MB", "GB", "TB", "PB", "EB"}; + int unit_index = 0; + double view_size = (double)size; + while (view_size >= 1024) + { + view_size /= 1024; + unit_index++; + } + snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// 単体テスト用 +// 外部からの関数実行によりテスト困難なテストケースを記載する。 +// + +/** + * KcMemoryDump_dump_message 単体テスト用関数。 + * + * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。 + * info_rest_size が 0 未満の場合は、何もしません。 + * info_rest_size を超えるメッセージは書き込まれません。 + * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。 + * + * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、 + * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。) + * (1) info の rest_size が 0 未満の場合は、何もしない。 + * (2) info の rest_size を超えるメッセージは書きこまない。 + * + * @param info_write_ptr 書込み用ポインタ + * @param info_rest_size サイズ + * @param msg 書き込むメッセージ + */ +void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg) +{ + KcMemoryDumpBufferInfo info = { + .write_ptr = info_write_ptr, + .rest_size = info_rest_size, + }; + KcMemoryDump_dump_message(&info, msg); +} diff --git a/modules/src/kc_memory_entry.c b/modules/src/kc_memory_entry.c new file mode 100644 index 0000000..b855508 --- /dev/null +++ b/modules/src/kc_memory_entry.c @@ -0,0 +1,90 @@ +/** + * @file kc_memory_entry.c + * @brief KC メモリ管理 Entry サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +#include +#include "kc_memory_entry_inner.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// 定数定義 +// + +/** パディング */ +#define KC_MEMORY_PADDING (sizeof(void *) * 2) + +/** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ +KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line) +{ + KcMemoryEntry *new_entry; + if ((entry == NULL) && (alignment > 0)) + { // アライメント指定でメモリを確保する。 + new_entry = (KcMemoryEntry *)aligned_alloc(alignment, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + else + { + new_entry = (KcMemoryEntry *)realloc(entry, + (size_t)(size + sizeof(KcMemoryEntry) + KC_MEMORY_PADDING)); + } + + KcMemoryEntry_set(new_entry, size, mark, file, func, line); + return new_entry; +} + +/** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ +void KcMemoryEntry_delete(KcMemoryEntry *entry) +{ + entry->mark = KC_MEMORY_DELETED; + entry->size = 0; + free(entry); +} + +/** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ +void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line) +{ + if (entry != NULL) + { + entry->size = size; + entry->mark = mark; + entry->file = file; + entry->func = func; + entry->line = line; + entry->_prev = NULL; + entry->_next = NULL; + entry->data = (entry + 1); + } +} diff --git a/modules/src/kc_memory_entry_inner.h b/modules/src/kc_memory_entry_inner.h new file mode 100644 index 0000000..77743b7 --- /dev/null +++ b/modules/src/kc_memory_entry_inner.h @@ -0,0 +1,67 @@ +/** + * @file kc_memory_entry_inner.h + * @brief KC メモリ管理 Entry サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_ENTRY_INNER_H +#define KC_MEMORY_ENTRY_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * KcMemoryEntry を構築します。 + * entry が NULL の場合、新規に KeMemoryEntry を構築します。 + * entry が NULL でない場合、entry の管理するメモリサイズを変更し、各種値を更新します。 + * 構築に失敗した場合、NULL を返します。 + * + * @param entry メモリエントリ + * @param alignment アライメント + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + * @return 構築した KcMemoryEntry + */ + KcMemoryEntry *KcMemoryEntry_new(KcMemoryEntry *entry, size_t alignment, size_t size, + KcMemoryMark mark, const char *file, const char *func, int line); + /** + * KcMemoryEntry を破棄します。 + * + * @param entry 破棄するメモリエントリ + */ + void KcMemoryEntry_delete(KcMemoryEntry *entry); + + /** + * 指定された entry に、指定された値を設定します。 + * entry が NULL の場合、何もしません。 + * + * @param entry メモリエントリ + * @param size メモリサイズ + * @param mark メモリ状態 + * @param file メモリ確保ファイル名 + * @param func メモリ確保関数名 + * @param line メモリ確保行番号 + */ + void KcMemoryEntry_set(KcMemoryEntry *entry, + size_t size, KcMemoryMark mark, const char *file, const char *func, int line); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_ENTRY_INNER_H diff --git a/modules/src/kc_memory_listener.c b/modules/src/kc_memory_listener.c new file mode 100644 index 0000000..29ff2a0 --- /dev/null +++ b/modules/src/kc_memory_listener.c @@ -0,0 +1,102 @@ +/** + * @file kc_memory_listener.c + * @brief KC メモリ管理 Listener サブモジュール + * @copyright 2003 - 2023 Nomura Kei + */ +#include +#include + +#include +#include +#include "kc_memory_listener_inner.h" + +// ------------------------------------- +// allocate +// ------------------------------------- +/** + * メモリ確保の際に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_allocate(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// free +// ------------------------------------- +/** + * メモリ解放の差異に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_free(const KcMemoryEntry *entry) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); +} + +// ------------------------------------- +// error +// ------------------------------------- +/** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg) +{ + // Nothing to do. + UNUSED_VARIABLE(entry); + UNUSED_VARIABLE(msg); +} + +/** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ +void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ALLOC] %s", buff); + } +} + +/** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ +void KcMemoryListener_dump_free(const KcMemoryEntry *entry) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[FREE ] %s", buff); + } +} + +/** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ +void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg) +{ + if (entry != NULL) + { + char buff[256]; + KcMemoryDump_dump(buff, sizeof(buff), entry, 16, true, true, 130); + fprintf(stderr, "[ERROR] %s", buff); + } + fprintf(stderr, msg); +} diff --git a/modules/src/kc_memory_listener_inner.h b/modules/src/kc_memory_listener_inner.h new file mode 100644 index 0000000..784c0f8 --- /dev/null +++ b/modules/src/kc_memory_listener_inner.h @@ -0,0 +1,73 @@ +/** + * @file kc_memory_listener_inner.h + * @brief KC メモリ管理 Listener サブモジュール (内部用) + * @copyright 2003 - 2023 Nomura Kei + * @depends + * kc.h + * kc_memory_mark.h + * kc_memory_entry.h + * + * KcMemoryEntry の操作をする関数群を格納したサブモジュールです。 + */ +#ifndef KC_MEMORY_LISTENER_INNER_H +#define KC_MEMORY_LISTENER_INNER_H + +#include + +#ifdef __cplusplus +extern "C" +{ + namespace kc + { + using namespace std; +#endif + + /** + * メモリ確保時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に呼び出されるデフォルトのコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に呼び出されるデフォルトのコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_error(const KcMemoryEntry *entry, const char *msg); + + /** + * メモリ確保時に、確保されたメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 確保されたメモリエントリ + */ + void KcMemoryListener_dump_allocate(const KcMemoryEntry *entry); + + /** + * メモリ解放時に、解放されるメモリの情報を標準エラー出力するコールバック関数 + * + * @param entry 解放されるメモリエントリ + */ + void KcMemoryListener_dump_free(const KcMemoryEntry *entry); + + /** + * エラー発生時に、エラー情報を標準エラー出力するコールバック関数 + * + * @param entry エラー発生したメモリエントリ (NULL の場合あり) + * @param msg エラー発生時のメッセージ + */ + void KcMemoryListener_dump_error(const KcMemoryEntry *entry, const char *msg); + +#ifdef __cplusplus + } // namespace kc +} // extern "C" +#endif +#endif // KC_MEMORY_LISTENER_INNER_H diff --git a/modules/src/kc_memory_mark.c b/modules/src/kc_memory_mark.c new file mode 100644 index 0000000..ba246b2 --- /dev/null +++ b/modules/src/kc_memory_mark.c @@ -0,0 +1,36 @@ + +/** + * @file kc_memory_mark.c + * @brief KC メモリ管理 メモリ状態を扱うサブモジュール (mark) + * @copyright 2003 - 2023 Nomura Kei + */ +#include + +/** + * 指定されたメモリ状態(mark)に対応する文字列表現を返します。 + * 返される文字列は、次の通り + * - alloc : malloc, calloc, realloc によりメモリが確保された + * - new : new によりメモリが確保された + * - new[] : new[] によりメモリが確保された + * - delete : 削除済みメモリ + * - other : 不明 + * + * @param mark メモリ状態 + * @return メモリ状態に対応する文字列表現 + */ +const char *KcMemoryMark_to_string(int mark) +{ + switch (mark) + { + case KC_MEMORY_DELETED: + return "delete"; + case KC_MEMORY_ALLOCATED: + return "alloc "; + case KC_MEMORY_ALLOCATED_NEW: + return "new "; + case KC_MEMORY_ALLOCATED_NEW_ARRAY: + return "new[] "; + default: + return "other "; + } +} diff --git a/modules/src/kc_ut.c b/modules/src/kc_ut.c new file mode 100644 index 0000000..dcf722c --- /dev/null +++ b/modules/src/kc_ut.c @@ -0,0 +1,153 @@ +/** + * @file kc_ut.c + * @brief 単体テストモジュール + * @copyright 2003 - 2024 Nomura Kei + */ +#include +#include +#include +#include + +// 最大登録関数 +#define KC_UT_ENTRY_MAX (16384) +#define KC_UT_OUTPUT stderr + +/** + * テスト用関数リスト。 + */ +typedef struct KcUtEntry_ +{ + UtType type; + const char *title; + void (*func)(void); +} KcUtEntry; + +/** + * テスト管理情報 + */ +typedef struct +{ + int test_counter; //!< 実施テスト数 + int ok_counter; //!< OK 件数 + int ng_counter; //!< NG 件数 + int func_counter; //!< 登録関数の数 + int test_case; //!< テストケース数 + int run_index; //!< 実行関数インデックス + bool is_ng; //!< NG が発生したか否かの判定用 + KcUtEntry entry[KC_UT_ENTRY_MAX]; +} KcUtInfo; + +// プロトタイプ宣言 +static void KcUt_assert_handler(const char *msg); +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)); +static void KcUt_run(KcUt *ut); + +// 公開関数 + +/** + * 単体テスト用のインスタンスを取得します。 + * + * @return 単体テスト用の唯一のインスタンス + */ +KcUt *KcUt_get_instance(void) +{ + static KcUt instance; + static KcUtInfo info = { + .test_counter = 0, + .ok_counter = 0, + .ng_counter = 0, + .func_counter = 0, + .run_index = 0, + .test_case = 0, + .is_ng = false, + }; + if (instance._info != &info) + { // 初回のみ初期化する。 + instance.add = KcUt_add; + instance.run = KcUt_run; + instance._info = &info; + } + return &instance; +} + +// 内部関数 + +/** + * 単体テスト用 assert ハンドラ。 + * + * @param msg メッセージ + */ +static void KcUt_assert_handler(const char *msg) +{ + KcUt *ut = KcUt_get_instance(); + ((KcUtInfo *)ut->_info)->is_ng = true; + fprintf(stderr, KC_TERM_H_YEL "%s\n" KC_TERM_DEF KC_TERM_CLR, msg); + fprintf(stdout, KC_TERM_DEF KC_TERM_CLR); +} + +/** + * テストケースまたは、事前処理/事後処理の関数を追加します。 + * + * @param ut KcUT インスタンス + * @param type タイプ(UT_TESTCASE/UT_SETUP/UT_TEARDOWN/UT_OTHER) + * @param title タイトル + * @param func 追加する関数 + */ +static void KcUt_add(KcUt *ut, UtType type, const char *title, void (*func)(void)) +{ + KcUtInfo *info = (KcUtInfo *)ut->_info; + info->entry[info->func_counter].type = type; + info->entry[info->func_counter].title = title; + info->entry[info->func_counter].func = func; + info->func_counter++; + if (type == UT_TESTCASE) + { + info->test_case++; + } +} + +/** + * テストケースを実行します。 + * + * @param ut KcUT インスタンス + */ +static void KcUt_run(KcUt *ut) +{ // assert_handler を単体試験用ハンドラに切り替えておく。 + assert_handler = KcUt_assert_handler; + + const char *result; + KcUtInfo *info = (KcUtInfo *)ut->_info; + // KcMemory_start(true); + for (info->run_index = 0; info->run_index < info->func_counter; info->run_index++) + { + info->is_ng = false; + info->entry[info->run_index].func(); + if (info->entry[info->run_index].type == UT_TESTCASE) + { // テストケースの場合のみエラー判定&結果表示する。 + info->test_counter++; + if (!info->is_ng) + { // テスト成功 + info->ok_counter++; + result = KC_TERM_GRN "OK" KC_TERM_DEF; + } + else + { // テスト失敗 + info->ng_counter++; + result = KC_TERM_RED "NG" KC_TERM_DEF; + } + // 実行結果表示 + printf( + KC_TERM_BLD KC_TERM_CYN + "[No.%05d] %-65s [ %s" KC_TERM_CYN " ]\n" KC_TERM_DEF KC_TERM_CLR, + info->test_counter, + info->entry[info->run_index].title, + result); + fprintf(stderr, KC_TERM_DEF KC_TERM_CLR); + } + } + printf(KC_TERM_BLD KC_TERM_CYN "===== Result ==========================================================\n"); + printf(KC_TERM_GRN " Success : %-5d\n", info->ok_counter); + printf(KC_TERM_RED " Failuer : %-5d\n", info->ng_counter); + printf(KC_TERM_CYN " Total : %-5d\n" KC_TERM_DEF KC_TERM_CLR, info->test_counter); + printf("\n"); +} diff --git a/modules/src/main.c b/modules/src/main.c new file mode 100644 index 0000000..446efe2 --- /dev/null +++ b/modules/src/main.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +#ifndef UNITTEST +int main(int argc, char *argv[]) +#else +int main_exec(int argc, char *argv) +#endif +{ + KcMemory_start(true); + + UNUSED_VARIABLE(argc); + UNUSED_VARIABLE(argv); + char *buf = (char *)malloc(128); + char *buf2 = (char *)malloc(256); + UNUSED_VARIABLE(buf2); + buf[0] = 'A'; + buf[1] = '\n'; + buf[2] = '\0'; + printf("%s", buf); + assert_equals(124, 123); + assert_equals("ABC", "ABC"); + assert_equals(2.34, 2.33, 0.1); + assert_equals("\x01\x02", "\x01\x02\x03", 2); + return 0; +} diff --git a/modules/test.c b/modules/test.c new file mode 100644 index 0000000..12d2d8f --- /dev/null +++ b/modules/test.c @@ -0,0 +1,36 @@ +#include +#include +// #include +#include + + +void assert_equals_long(long expected, long actual) +{ + (void) expected; + (void) actual; + printf("-----\n"); +} +int add_i_i(int a, int b) +{ + return (a+b); +} +double add_i_d(int a, double b) +{ + printf("d\n"); + return (a + b); +} + +double add_d_d(double a, double b) +{ + printf("d d\n"); + return (a + b); +} +int main() +{ + // printf("%d\n", add(1,2)); + // printf("%d\n", add(1,2.2)); + // printf("%d\n", add(1.3,2)); + assert_equals(123,123); + return 0; +} + diff --git a/modules/test/Makefile b/modules/test/Makefile new file mode 100644 index 0000000..41c513f --- /dev/null +++ b/modules/test/Makefile @@ -0,0 +1,48 @@ +g ============================================================================== +# Makefile +# ============================================================================== +# +# TOPDIR : トップディレクトリ +# RULEDIR : Meke のルール一式が格納されているディレクトリ +# NAME : モジュール名 (拡張子を含めないこと) +# TARGET : モジュールファイル名 (拡張子を含めること) +# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) +# +TOPDIR ?= ../.. +RULEDIR ?= $(TOPDIR)/mk +NAME = ut.exe +TARGET = $(NAME) +SUBDIRS = +USE_SO_VERSION = + +# ------------------------------------------------------------------------------ +# *-cmd.mk : コマンド +# *-conf.mk : 設定 +# *-auto.mk : 自動設定 +# ------------------------------------------------------------------------------ +include $(TOPDIR)/config.mk +include $(RULEDIR)/*-cmd.mk +include $(RULEDIR)/*-conf.mk +include $(RULEDIR)/*-auto.mk +# ------------------------------------------------------------------------------ +# +# 以下、オプションを適宜変更してください。 +# + +INCLUDES += -I$(TOPDIR)/include -I../src +CFLAGS += +CXXFLAGS += +LDFLAGS += +LIBS += -L$(TOPDIR)/lib + +CLEAN_FILES += +CLEAN_DIRS += + +.DEFAULT_GOAL := all + +# ------------------------------------------------------------------------------ +# *-rule : ルール +# ------------------------------------------------------------------------------ +include $(RULEDIR)/*-rule.mk +# ------------------------------------------------------------------------------ + diff --git a/modules/test/src/test_list.c b/modules/test/src/test_list.c new file mode 100644 index 0000000..d9d811c --- /dev/null +++ b/modules/test/src/test_list.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +static void test_1(void) +{ + int *ptr = (int *)malloc(sizeof(int) * 4); + ptr[0] = 'A'; + ptr[1] = 'B'; + ptr[2] = 'C'; + ptr[3] = ptr[0] + ptr[1] * ptr[2]; + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_list(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "List テスト1", test_1); + ut->add(ut, UT_TESTCASE, "List テスト2", test_2); + ut->add(ut, UT_TESTCASE, "List テスト3", test_3); +} \ No newline at end of file diff --git a/modules/test/src/test_list_array.c b/modules/test/src/test_list_array.c new file mode 100644 index 0000000..5fa1256 --- /dev/null +++ b/modules/test/src/test_list_array.c @@ -0,0 +1,379 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_list_array_new(void); +static void test_list_array_add(void); +static void test_list_array_remove(void); +static void test_list_array_set(void); +static void test_list_array_search(void); +static void test_list_array_sort(void); +static void test_list_array_malloc_error(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_list_array(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "list_array new ArrayList", test_list_array_new); + ut->add(ut, UT_TESTCASE, "list_array add/get", test_list_array_add); + ut->add(ut, UT_TESTCASE, "list_array remove", test_list_array_remove); + ut->add(ut, UT_TESTCASE, "list_array set", test_list_array_set); + ut->add(ut, UT_TESTCASE, "list_array search", test_list_array_search); + ut->add(ut, UT_TESTCASE, "list_array sort", test_list_array_sort); + ut->add(ut, UT_TESTCASE, "list_array malloc error", test_list_array_malloc_error); +} + +/** + * ArrayList 生成/破棄。 + * + * @process KcList_new_ArrayList を実行する。。 + * @result ArrayList が生成されること。 + * + * @process KcList_delete にて ArrayList を破棄する。 + * @result ArrayList が破棄されること。 + */ +static void test_list_array_new(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + KcList_delete(list); +} + +/** + * ArrayList データ追加/取得。 + * + * @process 初回追加 (index = 0) + * @process 2つめ追加 (index = 1) + * @process 先頭に追加 (index = 0) + * @process 末尾に追加 (index = 負値(-1)) + * @process 追加(index 不正) + * @process 値取得(サイズ取得あり) + * @process 値取得(サイズ取得なし) + * @process 値取得 (index 不正) + */ +static void test_list_array_add(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 空 + bool is_empty = list->is_empty(list); + assert_true(is_empty); + + // 1つめ追加 + int val = 10; + bool res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + is_empty = list->is_empty(list); + assert_false(is_empty); + + // 2つめ追加 + val = 20; + res = list->add(list, 1, &val, sizeof(int)); + assert_true(res); + + // 先頭に追加 + val = 30; + res = list->add(list, 0, &val, sizeof(int)); + assert_true(res); + + // 末尾に追加 + val = 40; + res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + + // 追加不可位置への追加 + val = 50; + res = list->add(list, 10, &val, sizeof(int)); + assert_false(res); + + // 1つめ取得 + size_t size; + int *res_val = list->get(list, 0, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(30, *res_val); + + // 2つめ取得 + res_val = list->get(list, 1, &size); + assert_equals((int)sizeof(int), (int)size); + assert_equals(10, *res_val); + + // 3つめ取得(サイズ取得なし) + res_val = list->get(list, 2, NULL); + assert_equals(20, *res_val); + + // 4つめ取得(サイズ取得なし) + res_val = list->get(list, 3, NULL); + assert_equals(40, *res_val); + + // 存在しないデータ取得 + res_val = list->get(list, 4, NULL); + assert_null(res_val); + + KcList_delete(list); +} + +/** + * ArrayList データ削除。 + * + * @process 末尾削除 + * @process 中間削除 + * @process 先頭削除 + * @process 削除(index 不正) + * @process 残っているデータを確認 + */ +static void test_list_array_remove(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160}; + int default_size = 16; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + + // 末尾削除 (値、サイズ取得あり) + int rval; + size_t rsize; + bool ret = list->remove(list, (default_size - 1), &rval, &rsize); + assert_true(ret); + assert_equals(vals[(default_size - 1)], rval); + assert_equals((int)sizeof(int), (int)rsize); + int now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 値を追加して戻しておく + list->add(list, (default_size - 1), &vals[(default_size - 1)], sizeof(int)); + now_size = list->size(list); + assert_equals(default_size, now_size); + + // 中間削除 (値取得あり) + // 10, 20, <30>, 40, 50, ... + ret = list->remove(list, 2, &rval, NULL); + assert_true(ret); + assert_equals(vals[2], rval); + now_size = list->size(list); + assert_equals((default_size - 1), now_size); + + // 先頭削除 (値取得なし) + // <10>, 20, 40, 50, ... + ret = list->remove(list, 0, NULL, NULL); + assert_true(ret); + now_size = list->size(list); + assert_equals((default_size - 2), now_size); + + // 削除(index 不正) + ret = list->remove(list, default_size, NULL, NULL); + assert_false(ret); + + // 残り3つになるまで削除 (3つめを削除) + int rest_size = list->size(list); + while (rest_size > 3) + { + ret = list->remove(list, 3, NULL, NULL); + assert_true(ret); + rest_size = list->size(list); + } + + // 残っているデータの確認 + // 20, 40, 50 + int *res_val_1 = list->get(list, 0, NULL); + int *res_val_2 = list->get(list, 1, NULL); + int *res_val_3 = list->get(list, 2, NULL); + assert_equals(20, *res_val_1); + assert_equals(40, *res_val_2); + assert_equals(50, *res_val_3); + + bool is_empty = list->is_empty(list); + assert_false(is_empty); + + // クリア + list->clear(list); + is_empty = list->is_empty(list); + assert_true(is_empty); + + KcList_delete(list); +} + +/** + * ArrayList データ設定。 + */ +static void test_list_array_set(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 20, 30, 40, 50}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(5, now_size); + + // 値設定 (元の値, サイズ取得) + int set_value = 99; + int orig_val; + size_t orig_size; + bool ret = list->set(list, 1, &set_value, sizeof(int), &orig_val, &orig_size); + assert_true(ret); + assert_equals(20, orig_val); + assert_equals((int)sizeof(int), (int)orig_size); + int *res_val = list->get(list, 1, NULL); + assert_equals(99, *res_val); + + // 値設定 (元の値取得) + set_value = 98; + ret = list->set(list, 0, &set_value, sizeof(int), &orig_val, NULL); + assert_true(ret); + assert_equals(10, orig_val); + res_val = list->get(list, 0, NULL); + assert_equals(98, *res_val); + + now_size = list->size(list); + assert_equals(5, now_size); + + KcList_delete(list); +} + +/** + * ArrayList 検索。 + */ +static void test_list_array_search(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // 値が含まれることの確認 + // index_of, last_index_of の確認 + int c_vals[] = {10, 20, 30, 40, 50, 60}; + int c_vals_index[] = {0, 4, 1, 3, 2, 7}; + int c_vals_lindex[] = {5, 8, 6, 3, 2, 7}; + bool is_contains; + int res_index; + int res_lindex; + for (int i = 0; i < (int)(sizeof(c_vals) / sizeof(int)); i++) + { + is_contains = list->contains(list, &c_vals[i], sizeof(int)); + assert_true(is_contains); + + res_index = list->index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_index[i], res_index); + + res_lindex = list->last_index_of(list, &c_vals[i], sizeof(int)); + assert_equals(c_vals_lindex[i], res_lindex); + } + + // 値が含まれないことの確認 + int c_val = 99; + is_contains = list->contains(list, &c_val, sizeof(int)); + assert_false(is_contains); + + res_index = list->index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_index); + + res_lindex = list->last_index_of(list, &c_val, sizeof(int)); + assert_equals(-1, res_lindex); + + KcList_delete(list); +} + +// ソート用コンパレータ +static int test_list_array_sort_comparator( + const void *element1, size_t size1, + const void *element2, size_t size2, + void *args) +{ + int val1 = *((int *)element1); + int val2 = *((int *)element2); + assert_equals((int)sizeof(int), (int)size1); + assert_equals((int)sizeof(int), (int)size2); + assert_equals("ABCDEFG", (const char *)args); + return (val1 - val2); +} + +/** + * ArrayList ソート。 + */ +static void test_list_array_sort(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), 5); + assert_not_null(list); + + // 値設定 + int vals[] = {10, 30, 50, 40, 20, 10, 30, 60, 20}; + for (int i = 0; i < (int)(sizeof(vals) / sizeof(int)); i++) + { + int val = vals[i]; + bool res = list->add(list, -1, &val, sizeof(int)); + assert_true(res); + } + int now_size = list->size(list); + assert_equals(9, now_size); + + // ソート実施 + list->sort(list, test_list_array_sort_comparator, "ABCDEFG"); + int sorted_vals[] = {10, 10, 20, 20, 30, 30, 40, 50, 60}; + + KcIterator *ite = list->iterator(list, 0); + int sorted_index = 0; + while (ite->hasNext(ite)) + { + size_t res_size; + int *res_val = (int *)ite->next(ite, &res_size); + assert_equals((int)sizeof(int), (int)res_size); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + // index = 5 より取得 + ite = list->iterator(list, 5); + sorted_index = 5; + while (ite->hasNext(ite)) + { + int *res_val = (int *)ite->next(ite, NULL); + assert_equals(sorted_vals[sorted_index], *res_val); + sorted_index++; + } + KcIterator_delete(ite); + + KcList_delete(list); +} + +/** + * ArrayList メモリ確保失敗。 + */ + +static void test_list_array_malloc_error(void) +{ + KcList *list = KcList_new_ArrayList(sizeof(int), -1); + assert_null(list); +} \ No newline at end of file diff --git a/modules/test/src/test_lock_guard.c b/modules/test/src/test_lock_guard.c new file mode 100644 index 0000000..f00ed0b --- /dev/null +++ b/modules/test/src/test_lock_guard.c @@ -0,0 +1,94 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_lock_guard_init(void); +static void test_lock_guard_init_null(void); +static void test_lock_guard_init_invalid_mutex(void); +static void test_lock_guard_release_null(void); + +/** + * lock_guard 単体テストスイート + */ +void suite_lock_guard(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "lock_gurad init/release", test_lock_guard_init); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (NULL)", test_lock_guard_init_null); + ut->add(ut, UT_TESTCASE, "lock_guard init failure (invalid mutex)", test_lock_guard_init_invalid_mutex); + ut->add(ut, UT_TESTCASE, "lock_guard release (NULL)", test_lock_guard_release_null); +} + +/** + * lock_guard 初期化/解放動作確認 + * + * @process mtx_init を実施した mutex を渡し、kc_lock_guard_init を実行する。 + * @result lock_guard が生成されること。 + * + * @process lock_guard を渡し、kc_lock_guard_release を実行する。 + * @result lock_guard が解放され、lock_guard.mutex が NULL となること。 + */ +static void test_lock_guard_init(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(NULL指定) + * + * @process NULL を渡し、kc_lock_guard_init を実行する。 + * @result errno が EINVAL に設定され、エラーが発生すること。 + */ +static void test_lock_guard_init_null(void) +{ + KcLockGuard lock_guard = kc_lock_guard_init(NULL); + assert_equals(EINVAL, errno); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 初期化失敗動作確認(未初期化mutex指定) + * + * @process mtx_init を実施していない mutex を渡し、kc_lock_guard_init を実行する。 + * @result 初期化に失敗し、返された lock_gurad の mutex が NULL となること。 + */ +static void test_lock_guard_init_invalid_mutex(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + mtx_destroy(&mutex); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_null(lock_guard.mutex); +} + +/** + * lock_guard 解放(NULL) 指定 + * + * @process mutex が NULL の KcLockGuard を指定し、kc_lock_guard_release を実行する。 + * @result 特にエラー等発生しないこと。 + */ +static void test_lock_guard_release_null(void) +{ + mtx_t mutex; + mtx_init(&mutex, mtx_plain | mtx_recursive); + KcLockGuard lock_guard = kc_lock_guard_init(&mutex); + assert_not_null(lock_guard.mutex); + + // Lock Guard 解放 + kc_lock_guard_release(&lock_guard); + assert_null(lock_guard.mutex); + + kc_lock_guard_release(&lock_guard); +} diff --git a/modules/test/src/test_memory.c b/modules/test/src/test_memory.c new file mode 100644 index 0000000..ddf6d0d --- /dev/null +++ b/modules/test/src/test_memory.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static void test_1(void) +{ + assert_equals("a", "a"); +} + +static void test_2(void) +{ + assert_equals(1, 2); +} +static void setup(void) +{ + // NOP +} +static void test_3(void) +{ + assert_true(1 == 1); +} + +void test_memory(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト1", test_1); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト2", test_2); + ut->add(ut, UT_SETUP, "SETUP", setup); + ut->add(ut, UT_TESTCASE, "テスト3", test_3); +} diff --git a/modules/test/src/test_memory_dump.c b/modules/test/src/test_memory_dump.c new file mode 100644 index 0000000..2a4991d --- /dev/null +++ b/modules/test/src/test_memory_dump.c @@ -0,0 +1,456 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_dump(void); +static void test_memory_dump_no_binary(void); +static void test_memory_dump_no_ascii(void); +static void test_memory_dump_no_binary_no_ascii(void); +static void test_memory_dump_no_buffer(void); +static void test_memory_dump_no_column(void); +static void test_memory_dump_no_column_show_binary(void); +static void test_memory_dump_no_column_no_binary(void); +static void test_memory_dump_no_column_show_ascii(void); +static void test_memory_dump_no_column_no_ascii(void); +static void test_memory_dump_padding(void); +static void test_memory_dump_over_kb(void); +static void test_memory_dump_message_size_zero(void); +static void test_memory_dump_message_few_buf(void); + +/** + * memory_dump 単体テストスイート + */ +void suite_memory_dump(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_dump dump", test_memory_dump); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary)", test_memory_dump_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no ASCII)", test_memory_dump_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no binary, no ASCII)", test_memory_dump_no_binary_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no buffer)", test_memory_dump_no_buffer); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column)", test_memory_dump_no_column); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show binary)", test_memory_dump_no_column_show_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no binary)", test_memory_dump_no_column_no_binary); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, show ASCII)", test_memory_dump_no_column_show_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (no column, no ASCII)", test_memory_dump_no_column_no_ascii); + ut->add(ut, UT_TESTCASE, "memory_dump dump (padding)", test_memory_dump_padding); + ut->add(ut, UT_TESTCASE, "memory_dump dump (over KB)", test_memory_dump_over_kb); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (size zero)", test_memory_dump_message_size_zero); + ut->add(ut, UT_TESTCASE, "memory_dump_message dump message (few buffer)", test_memory_dump_message_few_buf); +} + +/** + * memory_dump テスト + * + * @process 適宜データを用意し、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し) + * + * @process 適宜データを用意し、バイナリ表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII 表示無し) + * + * @process 適宜データを用意し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示無し、ASCII 表示無し) + * + * @process 適宜データを用意し、バイナリ表示無し、ASCII 表示無しにて、KcMemoryDump_dump を実行する。 + * @result データのダンプが指定したバッファに出力されること。 + */ +static void test_memory_dump_no_binary_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 130); + assert_true(ret); + assert_equals("test_file:123 ( 5.000 B) [test_func] " + " " + " " + "\n", + buff); +} + +/** + * memory_dump テスト(バッファ不足 [指定カラム数に満たない]) + * + * @process 指定カラム数に対してバッファサイズが小さい状態で、KcMemoryDump_dump を実行する。 + * @result データダンプに失敗すること。 + */ +static void test_memory_dump_no_buffer(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[80]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_false(ret); +} + +/** + * memory_dump テスト(バイナリ表示指定、ASCII 表示指定 -> カラム不足により表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 55); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- " + "\n", + buff); +} + +/** + * memory_dump テスト(バイナリ表示指定、-> カラム不足により、バイナリ表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_binary(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 45); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、一部表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_show_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, true, 35); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( | ABCDE " + "\n", + buff); +} + +/** + * memory_dump テスト(ASCII表示指定、-> カラム不足により、ASCII表示不可) + * + * @process 小さいカラム数を指定し、KcMemoryDump_dump を実行する。 + * @result バイナリ表示、ASCII 表示のないダンプが表示されること。 + */ +static void test_memory_dump_no_column_no_ascii(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, false, 16); + assert_true(ret); + // 10 20 30 40 50 + // | | | | | + // 1234567890123456789012345678901234567890123456789012345 + assert_equals("test_file:123 ( " + "\n", + buff); +} + +/** + * memory_dump テスト(padding) + * + * @process カラムサイズを大きくとり、パディングされる状態とする。 + * @result 出力が指定されたカラムサイズとなるようにパディングされること。 + */ +static void test_memory_dump_padding(void) +{ + char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + KcMemoryEntry entry = { + .size = 5, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, false, false, 60); + assert_true(ret); + // 10 20 30 40 50 60 + // | | | | | | + // 1234567890123456789012345678901234567890123456789012345678901234 + assert_equals("test_file:123 ( 5.000 B) [test_func] " + "\n", + buff); +} + +/** + * memory_dump テスト(KB以上のメモリ確保) + * + * @process KB 以上のメモリを確保し、KcMemoryDump_dump を実行する。 + * @result サイズが単位付きで表示されること。 + */ +static void test_memory_dump_over_kb(void) +{ + char test_data[] = "ABCDEFGHIJK \x00\x81\x82\x83\x84"; + KcMemoryEntry entry = { + .size = (int)((double)12.056 * 1024), + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + + // KB + char buff[256]; + bool ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 12.056 KB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // MB + entry.size = (int)((double)65.432 * (1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 65.432 MB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); + // GB + entry.size = (int)((double)1.012 * (1024 * 1024 * 1024)); + ret = KcMemoryDump_dump(buff, sizeof(buff), &entry, 16, true, true, 130); + assert_true(ret); + assert_equals("test_file:123 ( 1.012 GB) [test_func] " + " | 41 42 43 44 45 46 47 48 49 4A 4B 20 00 81 82 83 " + " | ABCDEFGHIJK ...." + "\n", + buff); +} + +extern void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg); +/** + * [内部関数] dump_message テスト (バッファサイズ0) + * + * @process 残りサイズ 0 のバッファを渡して、KcMemoryDump_dump_message を実行する。 + * @result バッファに何も格納されないこと。 + */ +static void test_memory_dump_message_size_zero(void) +{ + char info_write_buf[5]; + int info_rest_size = 0; + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("", info_write_buf); +} + +/** + * [内部関数] dump_message テスト (バッファ不足) + * + * @process バッファより多いメッセージを渡す。 + * @result バッファに入る分のメッセージが格納されること。 + */ +static void test_memory_dump_message_few_buf(void) +{ + char info_write_buf[5]; + int info_rest_size = (int)sizeof(info_write_buf); + info_write_buf[0] = '\0'; + _UT_KcMemoryDump_dump_message(info_write_buf, info_rest_size, "MESSAGE"); + assert_equals("MESS", info_write_buf); +} +// test_file:123 ( 5.000 B) [test_func] | 41 42 43 44 45 -- -- -- -- -- -- -- -- -- -- -- | ABCDE +// bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry, +// int bytes, bool binary, bool ascii, int column); +// 0x01, 0x19, 0x20, 0x21, 0x31, 0x7e, 0x7f, 0x80, +// normal +// column 数が +// ファイル名:行番号 (size bytes) [関数名] が長い。 +// binary が true / false +// ascii が true/ false + +// KcMemoryDump_dump_info(&buff_info, entry, info_column); +// \ No newline at end of file diff --git a/modules/test/src/test_memory_entry.c b/modules/test/src/test_memory_entry.c new file mode 100644 index 0000000..e883e07 --- /dev/null +++ b/modules/test/src/test_memory_entry.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_entry_new(void); +static void test_memory_entry_new_alignment(void); +static void test_memory_entry_new_realloc(void); +static void test_memory_entry_set_null(void); + +/** + * memory_entry 単体テストスイート + */ +void suite_memory_entry(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_entry new/delete", test_memory_entry_new); + ut->add(ut, UT_TESTCASE, "memory_entry new (alignment)", test_memory_entry_new_alignment); + ut->add(ut, UT_TESTCASE, "memory_entry new (realloc)", test_memory_entry_new_realloc); + ut->add(ut, UT_TESTCASE, "memory_entry set (NULL Entry)", test_memory_entry_set_null); +} + +/** + * memory_entry 生成/破棄 + * + * @process KcMemoryEntry_new を用いて KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, 0, 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + + KcMemoryEntry_delete(entry); + // entry->mark には、KC_MEMORY_DELETED を格納してから破棄されているが、 + // free 後のメモリ領域は、確認不可。 +} + +/** + * memory_entry 生成 (alignment指定) + * + * @process alignment を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に設定されていること。 + */ +static void test_memory_entry_new_alignment(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + assert_not_null(entry); + assert_equals(KC_MEMORY_ALLOCATED, entry->mark); + assert_equals(10, entry->size); + assert_equals("test_file", entry->file); + assert_equals("test_func", entry->func); + assert_equals(34, entry->line); + KcMemoryEntry_delete(entry); +} + +/** + * memory_entry 生成 (entry 指定) + * + * @process entry を指定して KcMemoryEntry を生成する。 + * @result KcMemoryEntry が生成されること。各値が指定された値に更新されていること。 + */ +static void test_memory_entry_new_realloc(void) +{ + KcMemoryEntry *entry = KcMemoryEntry_new( + NULL, sizeof(int), 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 34); + KcMemoryEntry *new_entry = KcMemoryEntry_new( + entry, 0, 250, KC_MEMORY_ALLOCATED, "test_file2", "test_func2", 123); + assert_not_null(new_entry); + assert_equals(KC_MEMORY_ALLOCATED, new_entry->mark); + assert_equals(250, new_entry->size); + assert_equals("test_file2", new_entry->file); + assert_equals("test_func2", new_entry->func); + assert_equals(123, new_entry->line); + + KcMemoryEntry_delete(new_entry); +} + +/** + * memory_entry 設定 + * + * @process NULL を指定して、KcMemoryEntry_set を用いて各値を設定する。 + * @result エントリが NULL のため、何も処理されないこと。 + */ +static void test_memory_entry_set_null(void) +{ + KcMemoryEntry_set(NULL, + 10, KC_MEMORY_ALLOCATED, "test_file", "test_func", 123); +} diff --git a/modules/test/src/test_memory_listener.c b/modules/test/src/test_memory_listener.c new file mode 100644 index 0000000..5927064 --- /dev/null +++ b/modules/test/src/test_memory_listener.c @@ -0,0 +1,146 @@ +#include +#include + +#include +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_listener_allocate(void); +static void test_memory_listener_free(void); +static void test_memory_listener_error(void); +static void test_memory_listener_dump_allocate(void); +static void test_memory_listener_dump_allocate_null(void); +static void test_memory_listener_dump_free(void); +static void test_memory_listener_dump_free_null(void); +static void test_memory_listener_dump_error(void); +static void test_memory_listener_dump_error_null(void); + +/** + * memory_listner 単体テストスイート + */ +void suite_memory_listener(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_listener_allcate", test_memory_listener_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_free", test_memory_listener_free); + ut->add(ut, UT_TESTCASE, "memory_listener_error", test_memory_listener_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate", test_memory_listener_dump_allocate); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_allocate(NULL)", test_memory_listener_dump_allocate_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free", test_memory_listener_dump_free); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_free(NULL)", test_memory_listener_dump_free_null); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error", test_memory_listener_dump_error); + ut->add(ut, UT_TESTCASE, "memory_listener_dump_error(NULL)", test_memory_listener_dump_error_null); +} + +static char test_data[] = "ABCDEFG \x00\x81 0123456789"; +static KcMemoryEntry test_entry = { + .size = 14, + .mark = KC_MEMORY_ALLOCATED, + .file = "test_file", + .func = "test_func", + .line = 123, + ._prev = NULL, + ._next = NULL, + .data = test_data}; + +/** + * KcMemoryListner_allocate 動作確認 + * + * @process KcMemoryListener_allocate を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_allocate(void) +{ + KcMemoryListener_allocate(&test_entry); +} + +/** + * KcMemoryListner_free 動作確認 + * + * @process KcMemoryListener_free を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_free(void) +{ + KcMemoryListener_free(&test_entry); +} + +/** + * KcMemoryListner_error 動作確認 + * + * @process KcMemoryListener_error を実行する。 + * @result 何も発生しない。(空の関数が実行される) + */ +void test_memory_listener_error(void) +{ + KcMemoryListener_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_allocate 動作確認 + * + * @process KcMemoryListener_dump_allocate を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_allocate(void) +{ + KcMemoryListener_dump_allocate(&test_entry); +} + +/** + * KcMemoryListner_dump_allocate (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_allocate を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_allocate_null(void) +{ + KcMemoryListener_dump_allocate(NULL); +} + +/** + * KcMemoryListner_dump_free 動作確認 + * + * @process KcMemoryListener_dump_free を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_free(void) +{ + KcMemoryListener_dump_free(&test_entry); +} + +/** + * KcMemoryListner_dump_free (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_free を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_free_null(void) +{ + KcMemoryListener_dump_free(NULL); +} + +/** + * KcMemoryListner_dump_error 動作確認 + * + * @process KcMemoryListener_dump_error を実行する。 + * @result 指定されたメモリがダンプされること。 + */ +void test_memory_listener_dump_error(void) +{ + KcMemoryListener_dump_error(&test_entry, "ERROR\n"); +} + +/** + * KcMemoryListner_dump_error (NULL 指定) 動作確認 + * + * @process 引数に NULL を渡し、KcMemoryListener_dump_error を実行する。 + * @result 何も起こらないこと。 + */ +void test_memory_listener_dump_error_null(void) +{ + KcMemoryListener_dump_error(NULL, "ERROR\n"); +} diff --git a/modules/test/src/test_memory_mark.c b/modules/test/src/test_memory_mark.c new file mode 100644 index 0000000..cd518f9 --- /dev/null +++ b/modules/test/src/test_memory_mark.c @@ -0,0 +1,87 @@ +#include +#include + +#include +#include +#include +#include + +// プロトタイプ宣言 +static void test_memory_mark_allocated(void); +static void test_memory_mark_allocated_new(void); +static void test_memory_mark_allocated_new_array(void); +static void test_memory_mark_deleted(void); +static void test_memory_mark_other(void); + +/** + * memory_mark 単体テストスイート + */ +void suite_memory_mark(void) +{ + KcUt *ut = KcUt_get_instance(); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED", test_memory_mark_allocated); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW", test_memory_mark_allocated_new); + ut->add(ut, UT_TESTCASE, "memory_mark ALLOCATED_NEW_ARRAY", test_memory_mark_allocated_new_array); + ut->add(ut, UT_TESTCASE, "memory_mark DELETED", test_memory_mark_deleted); + ut->add(ut, UT_TESTCASE, "memory_mark OTHER", test_memory_mark_other); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED) + * + * @process KC_MEMORY_ALLOCATED の文字列表現を取得する。 + * @result 'alloc ' が取得されること。 + */ +static void test_memory_mark_allocated(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED); + assert_equals("alloc ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW) + * + * @process KC_MEMORY_ALLOCATED_NEW の文字列表現を取得する。 + * @result 'new ' が取得されること。 + */ +static void test_memory_mark_allocated_new(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW); + assert_equals("new ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_NEW_ARRAY) + * + * @process KC_MEMORY_ALLOCATED_NEW_ARRAY の文字列表現を取得する。 + * @result 'new[] ' が取得されること。 + */ +static void test_memory_mark_allocated_new_array(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_ALLOCATED_NEW_ARRAY); + assert_equals("new[] ", res); +} + +/** + * KcMemoryMark の文字列表現取得 (ALLOCATED_DELETED) + * + * @process KC_MEMORY_DELETED の文字列表現を取得する。 + * @result 'delete' が取得されること。 + */ +static void test_memory_mark_deleted(void) +{ + const char *res = KcMemoryMark_to_string(KC_MEMORY_DELETED); + assert_equals("delete", res); +} + +/** + * KcMemoryMark の文字列表現取得 (その他) + * + * @process 規定外の値を指定し、 KcMemoryMark_to_string を実行する。 + * @result 'other ' が取得されること。 + */ +static void test_memory_mark_other(void) +{ + const char *res = KcMemoryMark_to_string(0x12345678); + assert_equals("other ", res); +} diff --git a/modules/test/src/ut.c b/modules/test/src/ut.c new file mode 100644 index 0000000..41f9017 --- /dev/null +++ b/modules/test/src/ut.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +extern void suite_lock_guard(void); +extern void suite_memory_dump(void); +extern void suite_memory_entry(void); +extern void suite_memory_listener(void); +extern void suite_memory_mark(void); +extern void suite_list_array(void); + +int main(void) +{ + // UT Setup + // test_list(); + // test_memory(); + suite_lock_guard(); + suite_memory_dump(); + suite_memory_entry(); + suite_memory_listener(); + suite_memory_mark(); + suite_list_array(); + + KcMemory_start(false); + KcUt *ut = KcUt_get_instance(); + ut->run(ut); + + return 0; +} diff --git a/ut/Makefile b/ut/Makefile deleted file mode 100644 index 31febb2..0000000 --- a/ut/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -# ============================================================================== -# Makefile -# ============================================================================== -# -# TOPDIR : トップディレクトリ -# RULEDIR : Meke のルール一式が格納されているディレクトリ -# NAME : モジュール名 (拡張子を含めないこと) -# TARGET : モジュールファイル名 (拡張子を含めること) -# SUBDIR : サブディレクトリ (処理したい順に空白区切りで記述すること) -# -TOPDIR ?= .. -RULEDIR ?= $(TOPDIR)/mk -NAME = ut.exe -TARGET = $(NAME) -SUBDIRS = -USE_SO_VERSION = - -# ------------------------------------------------------------------------------ -# *-cmd.mk : コマンド -# *-conf.mk : 設定 -# *-auto.mk : 自動設定 -# ------------------------------------------------------------------------------ -include $(TOPDIR)/config.mk -include $(RULEDIR)/*-cmd.mk -include $(RULEDIR)/*-conf.mk -include $(RULEDIR)/*-auto.mk -# ------------------------------------------------------------------------------ -# -# 以下、オプションを適宜変更してください。 -# - -INCLUDES += -I$(TOPDIR)/include -CFLAGS += -CXXFLAGS += -LDFLAGS += -LIBS += -L$(TOPDIR)/lib -lkc -ldl - -CLEAN_FILES += -CLEAN_DIRS += - -.DEFAULT_GOAL := all - -# ------------------------------------------------------------------------------ -# *-rule : ルール -# ------------------------------------------------------------------------------ -include $(RULEDIR)/*-rule.mk -# ------------------------------------------------------------------------------ -