Memory Allocation Strategies
The allocator.h header defines a template-based memory allocator designed for specialized requirements, including secure memory clearing, memory alignment, and standard container compatibility.
Key Mechanisms
-
Alignment Control: By wrapping code blocks with
#pragma pack(push)and#pragma pack(pop), the library isolates its internal memory alignment requirements from the global compiler settings, enusring consistent behavior regardless of the surrounding project configuration. -
Rebind Support: To maintain compatibility with C++ Standard Library containers, the allocator implements the
rebindstructure. This allows containers to request an allocator instence for a different type (e.g., changing fromSecureAllocator<T>toSecureAllocator<U>), satisfying requirements for internal node allocations. -
Memory Management: The implementation leverages
::operator newto perform raw memory allocation, separating the memory reservation from object construction. This allows for fine-grained lifecycle management. Similarly,std::addressofis utilized to retrieve pointers to objects, ensuring the code remains robust even if classes override the unary&operator. -
Secure Deallocation: When memory is released, the allocator performs a zero-fill operation on the buffer before invoking
::operator delete, which is a common practice in sensitive data processing to prevent memory remnants.
template <typename T>
class CustomAllocator {
public:
using value_type = T;
template <typename U> struct rebind { using other = CustomAllocator<U>; };
T* allocate(std::size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t n) {
std::memset(p, 0, n * sizeof(T));
::operator delete(p);
}
template <typename... Args>
void construct(T* p, Args&&... args) {
::new (static_cast<void*>(p)) T(std::forward<Args>(args)...);
}
void destroy(T* p) {
p->~T();
}
};
Configuration and Portability
The config.h file acts as a central hub for platform-specific abstractions, macro definitions, and type aliasing.
Symbol Visibility
To manage library exports across different compilers (MSVC, GCC, Clang), the system uses specific preprocessor macros. When building a shared library (JSON_DLL_BUILD), the library marks symbols as exported. Conversely, when consuming the library as a client (JSON_DLL), it marks them as imported. This ensures binary compatibility and avoids symbol resolution errors.
Type Aliasing and Integration
JsonCPP provides a flexible type system that adapts based on preprocessor flags. For instance, the library defines its String type as a custom std::basic_string that utilizes the user-defined allocator:
namespace Json {
using AllocatorType = std::conditional_t<ENABLE_SECURE_ALLOC, SecureAllocator<char>, std::allocator<char>>;
using String = std::basic_string<char, std::char_traits<char>, AllocatorType>;
using IStringStream = std::basic_istringstream<char, std::char_traits<char>, AllocatorType>;
using OStringStream = std::basic_ostringstream<char, std::char_traits<char>, AllocatorType>;
}
This abstraction extends to integer types as well, ensuring that 64-bit integers are handled natively across both Windows (using __int64) and POSIX-compliant systems (using int64_t). Furthermore, the library employs a macro-based system for deprecation warnings, utilizing compiler-specific attributes like __attribute__((deprecated)) or __declspec(deprecated) to provide developers with clear feedback during the build process regarding obsolete APIs.