编译器指令#pragma section的使用

#pragma section指令可用于创建一个自定义分区,可以将全局变量或者函数放在这个自定义分区内部,实现各个模块之间的数据共享。

对于GNU C/C++编译器来说,直接使用__attribute__((__section__("xxx")))对变量或函数进行修饰即可自动创建好分区,将变量和函数放入对应分区。而在windows 的VC编译器下,必须结合#pragma section和__declspec(allocate("xxx"))两个指令才能实现该功能。

1. gcc/g++编译器

        gcc/g++编译器会自动生成分区的起始和结束边界,只需要extern 引入边界变量即可,具体生成的边界变量名可能因编译器版本的区别有差异,需要自行通过链接器查看确认。

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


typedef void(*calc_t)(int, int); 
typedef struct
{ 
    const char* name; 
    calc_t fn;
} node_t; 

// 仅 gcc/g++ 支持__attribute__, VC中有对应的__declspec也可以实现该功能
#define section "ss"
#define SEC __attribute__((__section__(section), aligned(sizeof(node_t)))) 
#define REGISTER_FUNC(Func) SEC node_t sec_##section_##Func = {.name=#Func,.fn=Func};

// 当section ss不存在时,链接器将会找不到变量__start_ss和__stop_ss,进而报错
// 如果不确认section的起始和终止变量名的时候,可以在编译阶段结束后
// 使用ld -verbose查看编译器生成的section起始和终止的变量名
extern size_t __start_ss;
extern size_t __stop_ss;

void f1(int a, int b) 
{ 
    printf("%s %d %d %d\n", __func__, __LINE__, a, b); 
}
REGISTER_FUNC(f1)

void f2(int a, int b) 
{ 
    printf("%s %d %d %d\n", __func__, __LINE__, a, b); 
}
REGISTER_FUNC(f2)


void* get_func(const char* name) 
{ 
    size_t i=0; 
    node_t* p;
    for (char* ptr = (char*)(&__start_ss); ptr < (char*)(&__stop_ss);)
    { 
        printf("node id=%d, node address=%#x, nodebytes=[%#x,%#x,%#x,%#x,%#x,%#x,%#x,%#x].\n",i++,ptr,*ptr,*(ptr+1),*(ptr+2),*(ptr+3),*(ptr+4),*(ptr+5),*(ptr+6),*(ptr+7));
        p = (node_t*)ptr;
        if (!strcmp(p->name, name))
        {  
            return (void*)p->fn; 
        }
        ptr+=sizeof(node_t);
    }
    return NULL;
}


int main(int argc, char** argv) 
{ 
    printf("f1 adddress = %#x,f2 address = %#x.\n",f1,f2);
    calc_t ff=(calc_t)get_func("f2"); 
    
    if(ff!=NULL)
        ff(1,2);
    return 0; 
}

2. VC编译器

        VC编译器没有提供section边界变量,因此需要借助他的section合并功能,自行维护两个边界变量。具体来说,VC链接器在链接的过程中,会将各编译单元中的section按照name的从小到大顺序排列,name的比较规则遵循strcmp的字符串比较逻辑。并且当name中有$符号时,还会执行相应的合并操作,比如创建了3个分区,分区名为 "name$a" "name$b" "name$c",则这3个分区经过排序后,发现它们可以合并,于是3个小分区就合并成为了一个大分区。

        借助这个特性,我们就很容易构造出分区的边界。参考如下代码:

// section_test.h

#pragma section("F1")
#pragma section("F1$z")

__declspec(allocate("F1")) __declspec(selectany) int section_start[];
__declspec(allocate("F1$z")) __declspec(selectany) int section_end[];



//  module1.cpp
#pragma section("F1$a")

__declspec(allocate("F1$a")) int section_data_created_by_module1=5;



// module2.cpp
#pragma section("F1$m")

__declspec(allocate("F1$m")) int section_data_created_by_module2=10;


// ...


// main.cpp
#include<stdio.h>
#include "section_test.h"

int main(int argc, char* argv[])
{
    int* ss = &section_start[0];
    int* ee = &section_end[0];
    // 创建的section大小由编译器相关参数确定,vs2013中测试时默认是260个字节。可以放65个int数据
    printf("section size = %d, element count=%d.\n",(ee-ss)*sizeof(int),ee-ss);
    int id=0;
    for(;ss<ee;ss++,id++)
    {
        if(*ss)printf("find a valid element in this section, id=%d, value=%d.\n",id,*ss);
        //section被初始化的时候全部置0,所以*ss为0的时候表明此处未使用
    }
    return 0;
}

3. C/C++的反射

 之前写过一篇关于C/C++反射博文,当时是用宏定义实现的静态注册(c++ 单例模式+反射机制_c++ 单例模式怎么实现反射_MOONICK的博客-CSDN博客)。现在通过section这个功能,实现反射会更加容易,代码扩展性也更高。

4. 跨进程数据共享

如果在dll动态链接库中创建section,将变量放到section内,加载同一个dll链接库的进程可以共享这些变量。如下图App1和App2都加载了如同一份dll库,其中放在SharedData分区中的g_cont实现了跨进程共享。(补充一点:在windows操作系统中,当一个dll被加载时,操作系统会自动调用一次DLLMain函数,当进程正常退出释放dll时,会再一次调用DLLMain函数,可以在该函数中做一些初始化和资源清理的动作。windows查看进程信息: wmic process where ProcessId="1234" get /format:value)。

//DLLTEST.cpp   将该文件编为动态链接库DLLTEST.dll

#include <iostream>
#include <windows.h>
#pragma section("SharedData",read,write,shared)
#pragma comment(linker,"/SECTION:SharedData,RWS")//告知链接器,这个分区的而数据将被共享
__declspec(allocate("SharedData")) int g_cont = 0;
__declspec(dllexport) BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	printf("DllMain called for reason = %d.\n", ul_reason_for_call);
	return TRUE;
}

extern "C" 
{
	__declspec(dllexport) int __stdcall GetSharedVariable()
	{
		g_cont++;
		printf("GetSharedVariable called %d times.\n", g_cont);
		return g_cont;
	}
}
// App1 

#include <iostream>
#include <windows.h>

//#pragma comment(lib, "C:\\Users\\projects\\Vsproj\\DLLTEST\\x64\\Release\\DLLTEST.lib")
//__declspec(dllimport) BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved);
//__declspec(dllimport) int __stdcall GetSharedVariable();
//int AutoLoadMain(int argc, char* argv[])
//{
//	//BOOL res = DllMain(0,0,0);
//	int result = GetSharedVariable();
//	printf("App1 shared count = %d.\n", result);
//	getchar();
//	return 0;
//}

typedef int(__stdcall *FuncType)();
int LoadByhandMain(int argc, char* argv[])
{
	HMODULE handle = LoadLibrary("C:/Users/projects/Vsproj/DLLTEST/x64/Release/DLLTEST.dll");
	FuncType GetSharedVariableLoaded = (FuncType)GetProcAddress(handle,"GetSharedVariable");
	int result = GetSharedVariableLoaded();
	printf("App1 shared count = %d.\n", result);
	getchar();
	FreeLibrary(handle);
	return 0;
}

int main(int argc, char* argv[])
{
	LoadByhandMain(argc,argv);
	return 0;
}
// App2 

#include <iostream>
#include <windows.h>

typedef int(__stdcall* FuncType)();
int main(int argc, char* argv[])
{
	HMODULE handle = LoadLibrary("C:/Users/projects/Vsproj/DLLTEST/x64/Release/DLLTEST.dll");
	FuncType GetSharedVariableLoaded = (FuncType)GetProcAddress(handle, "GetSharedVariable");
	int result = GetSharedVariableLoaded();
	printf("App2 shared count = %d.\n", result);
	FreeLibrary(handle);
	getchar();
	return 0;
}

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值