1.10 static
C语言中的声明 (declaration)和定义 (definition)是两个容易混淆的概念。 声明只是给变量、函数、结构体、联合体命名,表明程序有该变量、函数、结构体、联合体。 定义是具体给变量分配存储空间、给出函数的具体实现、指明结构体和联合体成员。
/*
* 1.10:static成员变量的内存
* 1. 区分C语言的声明和定义;声明只是宣称符号;定义是分配内存,以及初始化内存。
* 2. esp ebp是栈的上下边界;
*/
class Account {
public:
static double m_rate; // declaration声明
static void set_rate(const double& x) { m_rate = x; } // 类的静态函数没有this指针
};
double Account::m_rate = 8.0; // 静态成员变量必须在类外初始化 defination
/*
*单例的设计
*
在 Meyers 单例模式中,单例对象在需要的时候才会被创建,而不是在程序启动时就创建。这样做可以延迟对象的创建时间,只有在第一次使用单例对象时才会创建实例。
这种模式一般使用静态局部变量来实现。当函数第一次被调用时,静态局部变量会被初始化,且仅在第一次调用时初始化一次,之后的调用都会复用已经存在的对象
*/
class A {
public:
static A& getInstance() { return a; }// 对外获取成员变量
private:
A() {}; // 构造函数是私有的
static A a; // 成员变量静态只有一份, 坏处,这一份一直存在全局变量区,占用内存,希望用的时候才开始创建
A(const A&) {};
A& operator=(const A&) {};
};
class B { // meyers singleton,
public:
static B& getInsatance();
private:
B() {}; // 构造函数只调用一次
// 还需要防止用户拷贝构造和赋值构造,确保外部无法直接创建实例
B(const B&) {};
B& operator=(const B&) {};
};
B& B::getInsatance() {
static B b; // 重复执行这一句的话,是在干嘛?重复申请同名的全局变量
return b;
}
//在使用 Meyers 单例模式时,重复调用 `Singleton::getInstance()` 这句代码并不会导致重复创建 `instance` 的问题。这是因为在 C++11 标准之后,静态局部变量的初始化是线程安全的。
// 在第一次调用 `getInstance()` 时,`instance` 被初始化,之后的调用不会再重新初始化 `instance`,而是直接返回已经存在的 `instance`。
//这种行为是由 C++11 标准中的 "static initialization order fiasco" (静态初始化顺序混乱)问题得到解决的。
// 根据 C++11 标准,静态局部变量的初始化在第一次调用函数时进行,且初始化是线程安全的。这意味着在多线程环境下,静态局部变量的初始化不会出现竞态条件。
//因此,每次调用 `Singleton::getInstance()` 时,都会返回同一个 `instance` 实例,而不会重新创建新的实例。
/* 类模版 */
template <class T>
inline
const T& min(const T& a, const T& b) // 把比较写为一个模版函数
{
return a < b; // 通过重载操作符的形式来实现
}
class stone {
public:
stone(int w=0, int h=0, int we=0):_w(w),_h(h),_weight(we)
{}
bool operator<(const stone& rhs) const // const不改变data的操作
{
return _weight < rhs._weight;
}
private:
int _w, _h, _weight;
};
int main(char argc, char** argv) {
Account a; // 判断a和m_rate的地址区别,应该不在一个地方
return -1;
}
在使用 Meyers 单例模式时,重复调用 Singleton::getInstance()
这句代码并不会导致重复创建 instance
的问题。这是因为在 C++11 标准之后,静态局部变量的初始化是线程安全的。在第一次调用 getInstance()
时,instance
被初始化,之后的调用不会再重新初始化 instance
,而是直接返回已经存在的 instance
。
这种行为是由 C++11 标准中的 “static initialization order fiasco” (静态初始化顺序混乱)问题得到解决的。根据 C++11 标准,静态局部变量的初始化在第一次调用函数时进行,且初始化是线程安全的。这意味着在多线程环境下,静态局部变量的初始化不会出现竞态条件。
因此,每次调用 Singleton::getInstance()
时,都会返回同一个 instance
实例,而不会重新创建新的实例。
dlopen库:类的函数和变量在内存中是如何表现的?
在Linux下通过dlopen()
方式加载了共享库(.so
库)后,如果库中包含了类的定义和函数实现,你需要使用动态加载库的技术来调用这些函数。
首先,假设你已经加载了共享库并且获取了函数的指针,下面是一个简单的示例:
#include <dlfcn.h>
#include <iostream>
int main() {
// 加载共享库
void* handle = dlopen("libexample.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error: " << dlerror() << std::endl;
return 1;
}
// 获取函数指针
typedef void (*FuncPtr)();
FuncPtr func = (FuncPtr)dlsym(handle, "exampleFunction");
if (!func) {
std::cerr << "Error: " << dlerror() << std::endl;
dlclose(handle);
return 1;
}
// 调用函数
func();
// 关闭共享库
dlclose(handle);
return 0;
}
上述代码通过dlsym()
函数获取了共享库中名为exampleFunction
的函数的地址,并通过函数指针func
来调用它。
如果共享库中包含了类的定义和成员函数的实现,你可以通过类的工厂函数来创建类的实例,然后调用其成员函数。下面是一个示例:
假设有一个名为ExampleClass
的类,共享库中包含了它的定义和实现:
// exampleclass.h
#pragma once
class ExampleClass {
public:
ExampleClass();
void someMethod();
};
// exampleclass.cpp
#include "exampleclass.h"
#include <iostream>
ExampleClass::ExampleClass() {
std::cout << "ExampleClass constructor\n";
}
void ExampleClass::someMethod() {
std::cout << "someMethod called\n";
}
在你的主程序中,你可以通过工厂函数来创建ExampleClass
的对象,然后调用其成员函数:
#include <dlfcn.h>
#include <iostream>
int main() {
// 加载共享库
void* handle = dlopen("libexample.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error: " << dlerror() << std::endl;
return 1;
}
// 获取工厂函数指针
typedef ExampleClass* (*CreateFuncPtr)();
CreateFuncPtr createFunc = (CreateFuncPtr)dlsym(handle, "createExampleClass");
if (!createFunc) {
std::cerr << "Error: " << dlerror() << std::endl;
dlclose(handle);
return 1;
}
// 创建对象
ExampleClass* obj = createFunc();
if (obj) {
// 调用对象的成员函数
obj->someMethod();
// 释放对象
delete obj;
}
// 关闭共享库
dlclose(handle);
return 0;
}
在上述代码中,createExampleClass()
是一个工厂函数,用于创建ExampleClass
的对象。通过dlsym()
函数获取该工厂函数的地址,并调用它来创建对象。然后,你可以使用返回的对象指针调用类的成员函数。
总之,通过dlsym()
函数获取到的函数指针和变量地址,可以直接用于调用函数和访问变量,就像你在静态链接时一样。
实际上,创建出了对象,但实际并不能通过对象去调用类的成员函数。不是不能调用,是符号解析是打架了的。
如果在同一共享库中,通过对象调用成员函数失败,可能有几种原因:
-
符号可见性问题:确保类的定义和成员函数的实现在共享库中是可见的。在C++中,类的定义通常放在头文件中,而成员函数的实现在源文件中,因此需要确保这些文件都正确地编译到了共享库中。
-
名称修饰:C++编译器会对函数名进行名称修饰(Name Mangling),以支持函数重载。这可能导致函数在共享库中的名称不是你期望的。在调用
dlsym()
时,你需要确保使用正确的名称来获取函数指针。 -
内存空间不一致:如果共享库是由不同版本的编译器或不同编译选项生成的,可能会导致内存布局不一致,从而导致对象的指针无法正确访问成员函数。确保共享库和主程序使用了相同的编译器和编译选项。
-
多态问题:如果你的类是多态的(包含虚函数),确保共享库和主程序使用了相同的虚函数表。否则,通过对象调用成员函数时可能会发生未定义的行为。
-
库加载顺序:确保你的库在加载时正确地链接到主程序中。特别是如果你的库依赖于其他库,确保它们被正确地加载并链接。
-
运行时库路径问题:确保共享库可以正确地被加载。在使用
dlopen()
加载共享库时,可能需要设置正确的运行时库路径(通过LD_LIBRARY_PATH
环境变量或-rpath
选项)来告诉动态链接器在哪里查找共享库。
检查这些方面可能有助于找到问题的根源。如果问题仍然存在,你可能需要更详细地分析共享库的构建过程、加载过程以及类的定义和成员函数的实现。
cuda代码同算力的关系
CUDA 是一种用于并行计算的平行计算架构和编程模型,主要针对 GPU 进行加速计算。TensorRT 则是 NVIDIA 开发的深度学习推理引擎,可以针对深度学习模型进行优化和加速,其中也包含了针对 CUDA 的优化。
不同的算力版本主要体现在 GPU 架构上,NVIDIA 的不同 GPU 系列和型号具有不同的算力(Compute Capability),算力代表了 GPU 在执行 CUDA 程序时的能力。每个 GPU 架构都会带来不同的指令集和硬件特性,因此需要针对不同的算力版本进行编译,以充分利用各种 GPU 的性能。
在 CUDA 代码编译和不同算力之间的主要差异包括以下几点:
-
指令集和功能支持:不同算力版本的 GPU 支持的指令集和硬件功能有所不同。在编写 CUDA 代码时,需要考虑到目标 GPU 的算力版本,以确保代码中使用的指令和功能在目标 GPU 上是有效的。
-
优化和性能:针对不同的算力版本,编译器可能会应用不同的优化策略和技术,以最大程度地利用 GPU 的性能。因此,在选择算力版本时,可以根据目标 GPU 的硬件特性和优化需求来进行选择。
-
兼容性:通常来说,较新的 CUDA 版本会支持更多的算力版本,并提供更多的新特性和优化。因此,为了获得更好的性能和功能,建议使用较新版本的 CUDA 编译器和运行时库。
总之,不同算力版本之间的主要差异在于 GPU 的硬件特性、指令集支持和性能优化,因此在编写和编译 CUDA 代码时,需要根据目标 GPU 的算力版本选择合适的编译选项和优化策略,以确保代码能够在目标 GPU 上正确运行并获得最佳性能。
nvcc
编译器根据不同算力做出选择的主要方式是通过编译选项来指定目标 GPU 的算力版本。在 CUDA 编译过程中,你可以使用 -arch
选项来指定目标 GPU 的架构和算力版本。
例如,要编译针对 Compute Capability 7.0 的 GPU 的代码,你可以使用以下命令:
nvcc -arch=sm_70 ...
这里的 -arch=sm_70
指定了目标 GPU 的算力版本,sm_70
表示 Compute Capability 7.0。根据你的需要,你可以将 sm_70
替换为其他支持的算力版本,以适配不同的 GPU 架构。
nvcc
编译器还提供了其他一些选项来指定目标 GPU 的硬件特性和优化选项,例如 -code
、-gencode
、-Xcompiler
等。你可以根据具体的需求和目标 GPU 的特性来选择合适的编译选项和优化策略。
总之,nvcc
编译器根据不同算力做出选择的关键在于选择合适的编译选项来指定目标 GPU 的算力版本,以确保编译生成的代码能够在目标 GPU 上正确运行并获得最佳性能。
最后发现是linux系统的差异,rokcy就不可以,ubuntu就可以