C language: singleton mode (hungry Chinese style)

definition

The original intention of the singleton mode is to ensure that there is only one unique instance of a certain data type in the entire runtime and runtime space, and to provide a global access interface.

We can have a deeper understanding of the singleton pattern from two perspectives of instance creation and access: (1) The data type has and only one instance can be created. Programmers cannot define instances of this type at will like ordinary data types. It is a castrated data type that restricts the definition and creation of instances of the type. (2) This access interface is the access interface of the globally unique instance, not a data access interface in the ordinary sense.

accomplish

The general singleton mode can be divided into the lazy type and the hungry type according to the timing relationship of object creation and invocation. The hungry type generally creates objects when the program starts, and is not lazy initialized; the lazy type is created when it is actually used, using lazy initialization.

Hungry style

Hungry style, just like Hungry, is created when the program starts, whether it needs it or not. In C++, it is generally declared as a global variable to implement the hungry style. The global variable will create a global variable object before the execution of the main function, and release the global variable after the execution of the main function ends.

The executable program has a global _CTOR_LIST function pointer array, and the compiler will add the constructor pointer of the global variable to the _CTOR_LIST when linking; then the executable program will traverse and execute the _CTOR_LIST before executing the main function. All function pointers, thus completing the construction of global variables.

The same executable program also has a global _DTOR_LIST function pointer array, the compiler will add the destructor pointer of the global variable to _DTOR_LIST when linking; after the executable program executes the main function, it will traverse and execute this _DTOR_LIST All the function pointers, thus completing the destruction of global variables.

Familiar with the construction process of C++ starved Chinese style global variables, we refer to the construction principle of the global variable principle to realize the C language hungry Chinese style. Fortunately, both GCC and MSVC provide corresponding mechanisms to call functions before and after main.

GCC

GCC can use the attribute keyword to declare constructor and destructor C functions. The function declared as constructor will be called before main, and the function declared as destructor will be called after main.

#include<stdio.h> 

// 声明一个constructor属性函数,此函数会在main之前运行
__attribute__((constructor)) void before() 
{
    
      
   printf("run function before main\n"); 
} 

// 声明一个destructor属性函数,此函数会在main之后运行
__attribute__((destructor)) void after() 
{
    
     
   printf("run function after main\n"); 
} 
  
int main(int argc, char **argv) 
{
    
     
   printf("run main function\n"); 
   return 0; 
}

Referring to the principle of executing the function before the GCC main function, we can implement the GCC version of the singleton version.

singleton declaration

#pragma once

#include <stdio.h>
#include <stdlib.h>

typedef  void  File;

typedef enum BOOL
{
    
    
	FALSE = 0,
	TRUE = 1,
}BOOL;

typedef struct tagFileManager
{
    
    
	File* (*mkFile)(const char* fileName, char const* mode);
	BOOL  (*rmFile)(const char*  fileName);
	int   (*write)(File* file, const char* buf, int size);
	BOOL  (*exit)(const char* fileName);
	BOOL  (*close)(const File* file);
}FileManager;

singleton implementation

#include "file_manager.h"

#include <io.h>

static FileManager g_fileManager;

typedef int constructor();

static File* mkFile(const char* fileName, char const* mode);
static BOOL rmFile(const char* fileName);
static int fileWrite(File* file, const char* buf, int size);
static BOOL isExit(const char* fileName);
static BOOL fileClose(const File* file);

File* mkFile(const char* fileName, char const* mode)
{
    
    
	FILE* file = NULL;
	if (0 == fopen_s(&file, fileName, mode))
	{
    
    
		return file;
	}
	
	return NULL;
}

BOOL rmFile(const char* fileName)
{
    
    
	if (isExit(fileName))
	{
    
    
		return !remove(fileName);
	}
}

int fileWrite(File* file, const char* buf, int size)
{
    
    
	return fwrite(buf, size, 1, file);
}

BOOL isExit(const char* fileName)
{
    
    
	return (_access(fileName, 0) == 0);
}

BOOL fileClose(const File* file)
{
    
    
	return !fclose(file);
}

__attribute__((constructor)) static int ctor()
{
    
    
	g_fileManager.exit = isExit;
	g_fileManager.mkFile = mkFile;
	g_fileManager.rmFile = rmFile;
	g_fileManager.write = fileWrite;
	g_fileManager.close = fileClose;
	return 0;
}

__attribute__((destructor)) static int dtor()
{
    
    
	g_fileManager.exit = NULL;
	g_fileManager.mkFile = NULL;
	g_fileManager.rmFile = NULL;
	g_fileManager.write = NULL;
	g_fileManager.close = NULL;
	return 0;
}

FileManager* fileManager()
{
    
    
	return &g_fileManager;
}

MSVC

MSVC implements by declaring code segment names, we declare two special segment names ".CRT XIU ", ". CRT XIU", ".CRTXIU"," . C R T XCU", the linker will putthe functions declared in the ".CRT XIU" section into the "C initialization function table", and also put the functions declared in the ". CRT XIU" section into In the "C initialization function table", it will also be declared in ".CRTThe functions in the "X I U " section are placed in the " C initialization function table " , and the functions declared in the ". C R T XCU " section are also placed in the "C++ initialization function table " The MSVC executable program will first traverse the "C initialization function table" and "C++ initialization function table" before executing the main function and execute the functions in sequence. After the main function is executed, it must be registered through _onexit().

#include <stdlib.h>

int before1()
{
    
    
    printf("run function before1 before main\n"); 
    return 0;
}

int before2()
{
    
    
    printf("run function before2 before main\n");  
    return 0;
}

int after()
{
    
    
    printf("run function after main\n");
    return 0;
}

typedef int constructor();

#pragma data_seg(".CRT$XIU")
static constructor *beforeTab1[] = {
    
    before1}; 
#pragma data_seg(".CRT$XCU") 
static constructor *beforeTab2[] = {
    
    before2};

#pragma data_seg()

int _tmain(int argc, _TCHAR *argv[])
{
    
    
    _onexit(after);
    printf("run main function\n");
    return 0;
}

Based on the principle analysis of executing functions before and after the MSVC main function, we can write the MSVC version of the Hungry Singleton Mode

singleton declaration

#pragma once

#include <stdio.h>
#include <stdlib.h>

typedef  void  File;

typedef enum BOOL
{
    
    
	FALSE = 0,
	TRUE = 1,
}BOOL;

typedef struct tagFileManager
{
    
    
	File* (*mkFile)(const char* fileName, char const* mode);
	BOOL  (*rmFile)(const char*  fileName);
	int   (*write)(File* file, const char* buf, int size);
	BOOL  (*exit)(const char* fileName);
	BOOL  (*close)(const File* file);
}FileManager;

FileManager* fileManager();

singleton implementation

#include "file_manager.h"

#include <io.h>

static FileManager g_fileManager;

typedef int constructor();

static File* mkFile(const char* fileName, char const* mode);
static BOOL rmFile(const char* fileName);
static int fileWrite(File* file, const char* buf, int size);
static BOOL isExit(const char* fileName);
static BOOL fileClose(const File* file);
static int ctor();
static int dtor();

File* mkFile(const char* fileName, char const* mode)
{
    
    
	FILE* file = NULL;
	if (0 == fopen_s(&file, fileName, mode))
	{
    
    
		return file;
	}
	
	return NULL;
}

BOOL rmFile(const char* fileName)
{
    
    
	if (isExit(fileName))
	{
    
    
		return !remove(fileName);
	}
}

int fileWrite(File* file, const char* buf, int size)
{
    
    
	return fwrite(buf, size, 1, file);
}

BOOL isExit(const char* fileName)
{
    
    
	return (_access(fileName, 0) == 0);
}

BOOL fileClose(const File* file)
{
    
    
	return !fclose(file);
}

static int ctor()
{
    
    
	g_fileManager.exit = isExit;
	g_fileManager.mkFile = mkFile;
	g_fileManager.rmFile = rmFile;
	g_fileManager.write = fileWrite;
	g_fileManager.close = fileClose;

	_onexit(dtor);

	return 0;
}

static int dtor()
{
    
    
	g_fileManager.exit = NULL;
	g_fileManager.mkFile = NULL;
	g_fileManager.rmFile = NULL;
	g_fileManager.write = NULL;
	g_fileManager.close = NULL;
	return 0;
}

#pragma data_seg(".CRT$XIU")
static constructor * before[] = {
    
     ctor };
#pragma data_seg()


FileManager* fileManager()
{
    
    
	return &g_fileManager;
}

Summarize

The singleton mode is to ensure that there is only one unique instance of a certain data type in the entire runtime and runtime space, and to provide a global access interface. This article introduces the purpose of the singleton and the Hungry implementation of the singleton. In the next unit, we will continue to discuss the lazy implementation of singletons.

Guess you like

Origin blog.csdn.net/liuguang841118/article/details/124898593