C++中Static作用和使用方法-zz

本文深入解析C++中static修饰符的用途与内部机制,包括如何使用静态数据成员和静态成员函数,以及它们的优势和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是static?
       static 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性。

    2、为什么要引入static?
       函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一 个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此 函数控制)。

    3、什么时候用static?
       需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

    4、static的内部机制:
       静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
       这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
      静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
      static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

    5、static的优势:
       可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的 值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

    6、引用静态数据成员时,采用如下格式:
         <类名>::<静态成员名>
    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
7、注意事项:
      (1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致
了它仅能访问类的静态数据和静态成员函数。
      (2)不能将静态成员函数定义为虚函数。
      (3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊
,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

      (4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就
产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W
indow系统结合,同时也成功的应用于线程函数身上。
      (5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问
时间,节省了子类的内存空间。
      (6)静态数据成员在<定义或说明>时前面加关键字static。
      (7)静态数据成员是静态存储的,所以必须对它进行初始化。
      (8)静态成员初始化与一般数据成员初始化不同:
       初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
       初始化时不加该成员的访问权限控制符private,public等;
           初始化时使用作用域运算符来标明它所属类;
           所以我们得出静态数据成员初始化的格式:
         <数据类型><类名>::<静态数据成员名>=<值>
      (9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有 重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

 

静态数据成员

  在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。

使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个 对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

静态数据成员的使用方法和注意事项如下:

1、静态数据成员在定义或说明时前面加关键字static。

2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:

<数据类型><类名>::<静态数据成员名>=<值>

这表明:

        (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。

(2) 初始化时不加该成员的访问权限控制符private,public等。

(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。

3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。

4、引用静态数据成员时,采用如下格式:

<类名>::<静态成员名>

如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
静态成员函数

  静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员函数中要引用非静态成员时,可通过对象来引用。


下面看一个例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()

}
};
void main( void )
{
Point pt;
pt.init();
pt.output();
}
这样编译是不会有任何错误的。
下面这样看
#include <iostream.h>
class Point
{
public:
void output()

}
static void init()

}
};
void main( void )
{
Point::output();
}
这样编译会处错,错误信息:illegal call of non-static member function,为什么?
因为在没有实例化一个类的具体对象时,类是没有被分配内存空间的。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()

}
static void init()

}
};
void main( void )
{
Point::init();
}
这时编译就不会有错误,因为在类的定义时,它静态数据和成员函数就有了它的内存区,它不属于类的任何一个具体对象。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()

}
static void init()
{
   x = 0;
   y = 0;
}
private:
int x;
int y;
};
void main( void )
{
Point::init();
}
编译出错:
illegal reference to data member 'Point::x' in a static member function
illegal reference to data member 'Point::y' in a static member function
在一个静态成员函数里错误的引用了数据成员,
还是那个问题,静态成员(函数),不属于任何一个具体的对象,那么在类的具体对象声明之前就已经有了内存区,
而现在非静态数据成员还没有分配内存空间,那么这里调用就错误了,就好像没有声明一个变量却提前使用它一样。
也就是说在静态成员函数中不能引用非静态的成员变量。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
   x = 0;
   y = 0;
   init(); 
}
static void init()
{

}
private:
int x;
int y;
};
void main( void )
{
Point::init();
}
好的,这样就不会有任何错误。这最终还是一个内存模型的问题,
任何变量在内存中有了自己的空间后,在其他地方才能被调用,否则就会出错。
好的再看看下面的例子:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
   x = 0;
   y = 0;
}
private:
static int x;
static int y;
};
void main( void )
{
Point::init();
}
编译:
Linking...
test.obj : error LNK2001: unresolved external symbol "private: static int Point::y"
test.obj : error LNK2001: unresolved external symbol "private: static int Point::x"
Debug/Test.exe : fatal error LNK1120: 2 unresolved externals
执行 link.exe 时出错.
可以看到编译没有错误,连接错误,这又是为什么呢?
这是因为静态的成员变量要进行初始化,可以这样:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
   x = 0;
   y = 0;
}
private:
static int x;
static int y;
};
int Point::x = 0;
int Point::y = 0;
void main( void )
{
Point::init();
}
在静态成员数据变量初始化之后就不会出现编译错误了。
再看看下面的代码:
#include <iostream.h>
class Point
{
public:
void output()
{
}
static void init()
{
   x = 0;
   y = 0;
}
private:
static int x;
static int y;
};
void main( void )
{
}
编译没有错误,为什么?
即使他们没有初始化,因为我们没有访问x,y,所以编译不会出错。 
C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。

在某些条件下,比如说在使用诸如pthread(它不支持类)此类的多线程库时,就必须使用静态的成员函数,因为其地址同C语言函数的地址兼容。这种铜限制就迫使程序员要利用各种解决办法才能够从静态成员函数访问到非静态数据成员。

第一个解决办法是声明类的所有数据成员都是静态的。运用这种方式的话,静态的成员函数就能够直接地访问它们,例如:

class Singleton
{
public:
   static Singleton * instance();
private:
   Singleton * p;
   static Lock lock;
};

Singleton * Singleton::instance()
{
lock.getlock(); // fine, lock is static
if (!p)
   p=new Singleton;
lock.unlock();
return p;
}

这种解决方法不适用于需要使用非静态数据成员的类。

访问非静态数据成员

将参照传递给需要考量的对象能够让静态的成员函数访问到对象的非静态数据:

class A
{
public:
   static void func(A & obj);
   intgetval() const; //non-static member function
private:
intval;
};

静态成员函数func()会使用参照obj来访问非静态成员val。

voidA::func(A & obj)
{
   int n = obj.getval();
}

将一个参照或者指针作为静态成员函数的自变量传递,就是在模仿自动传递非静态成员函数里this自变量这一行为。


本篇文章来源于:开发学院 http://edu.codepub.com   原文链接:http://edu.codepub.com/2010/0514/22687_4.php

<think>好的,我现在需要回答用户的问题:如何通过LLVM pass修改C/C++代码中的静态变量声明。首先,我需要理解用户的具体需求。用户提到的是静态变量声明,这包括全局静态变量局部静态变量吗?一般来说,静态变量在C/C++中有不同的作用域,但LLVM IR中处理它们的方式可能有所不同。 接下来,我应该回忆一下LLVM pass的基本知识。LLVM pass是用来对IR进行转换或分析的模块。用户想要修改静态变量的声明,这意味着他们可能需要在IR中找到这些变量的声明位置,然后进行修改,比如改变其类型、初始化值或其他属性。 首先,我需要确定在LLVM IR中,静态变量是如何表示的。全局静态变量会被表示为GlobalVariable对象,而局部静态变量可能被转换为带有内部链接的全局变量,或者在函数内部通过特定的结构来处理。例如,局部静态变量在C++中可能会有guard变量来控制初始化,这在IR中会有相应的表现。 然后,用户需要修改这些声明。可能的操作包括更改变量的名称、类型、链接属性,或者调整初始化值。例如,用户可能想将静态变量的初始化值从0改为其他数值,或者更改其可见性。 接下来,我需要考虑如何在LLVM pass中定位这些静态变量。对于全局静态变量,可以通过遍历模块中的全局变量(Module::global_begin()到Module::global_end())并检查其链接属性(比如是否是内部链接internal linkage)。对于局部静态变量,可能需要查找特定模式,比如被存储在某个结构体中,或者被特定的函数(如__cxx_global_var_init)初始化。 修改静态变量可能涉及到的API包括GlobalVariable::setLinkage(), GlobalVariable::setInitializer(), 以及可能的重命名操作setName()。需要注意的是,修改全局变量可能会影响其他部分的代码,尤其是如果变量被其他模块或函数引用的话。因此,必须确保所有使用该变量的地方都相应地更新。 此外,用户提到的是C/C++代码转换,因此还需要考虑不同的ABI编译器特定的行为。比如,不同的编译器(GCC vs Clang)在处理静态变量时可能有细微差别,但在LLVM IR层面,这些差异已经被抽象化了,所以主要关注IR层面的操作。 可能的步骤总结如下: 1. 编写一个LLVM pass,继承自ModulePass或FunctionPass,具体取决于需要处理的变量类型。 2. 遍历模块中的全局变量,识别出静态变量(链接属性为内部或私有)。 3. 对每个找到的静态变量进行所需的修改,比如调整链接属性、初始化值或类型。 4. 处理局部静态变量可能需要更深入的函数分析,比如查找特定指令或调用。 5. 测试修改后的IR,确保生成的代码符合预期,并且没有破坏原有逻辑。 另外,需要注意LLVM的版本,不同版本的API可能有变化。用户可能需要查阅对应版本的LLVM文档,确保使用的API正确。例如,GlobalVariable的构造函数可能需要指定ModuleType等参数。 可能遇到的问题包括如何正确处理初始值设定项(Initializer),特别是当初始值是一个复杂的常量表达式时。此外,修改变量类型可能导致IR不一致,需要确保所有使用该变量的指令都适应新类型。 最后,用户可能需要实际的代码示例来指导如何实现这个pass。因此,在回答中提供一个简单的例子会很有帮助,展示如何找到静态全局变量并修改其初始化值,同时说明如何处理局部静态变量的情况。</think>要修改C/C++代码中的静态变量声明,可以通过LLVM pass操作中间表示(IR)实现。以下是具体步骤示例: ### 一、静态变量的LLVM IR表示 1. **全局静态变量**:在IR中表现为`GlobalVariable`对象,链接属性为`internal`(C语言)或私有(C++匿名命名空间)[^1]。 ```llvm @static_var = internal global i32 0 ; C语言静态全局变量 ``` 2. **局部静态变量**:会被提升为带内部链接的全局变量,并通过控制标志保证单次初始化: ```llvm @_ZZL9func_nameE10static_var = internal global i32 0 ; C++函数内静态变量 ``` ### 二、实现LLVM pass的步骤 1. **创建ModulePass**:继承`llvm::ModulePass`并注册Pass ID ```cpp #include "llvm/Pass.h" #include "llvm/IR/GlobalVariable.h" struct StaticVarModPass : public llvm::ModulePass { static char ID; StaticVarModPass() : ModulePass(ID) {} bool runOnModule(llvm::Module &M) override { bool modified = false; for (auto &GV : M.globals()) { if (GV.hasInternalLinkage()) { // 识别静态变量 modifyStaticVar(GV); modified = true; } } return modified; } void modifyStaticVar(llvm::GlobalVariable &GV) { // 修改操作示例:将初始值设为42 GV.setInitializer(llvm::ConstantInt::get(GV.getValueType(), 42)); } }; ``` 2. **注册Pass并编译**: ```bash clang -c -I/llvm/include `llvm-config --cxxflags` pass.cpp -o pass.o clang -shared pass.o -o libPass.so ``` ### 三、应用Pass到代码 1. **通过opt工具执行转换**: ```bash clang -S -emit-llvm -Xclang -disable-O0-optnone test.c -o test.ll opt -load ./libPass.so -static-var-mod test.ll -o modified.ll ``` ### 四、实际应用示例 **原始C代码**: ```c static int counter = 0; void increment() { static int local_counter = 0; counter++; local_counter++; } ``` **转换后IR变化**: ```diff - @counter = internal global i32 0 + @counter = internal global i32 42 - @_ZZ9incrementE12local_counter = internal global i32 0 + @_ZZ9incrementE12local_counter = internal global i32 42 ``` ### 五、注意事项 1. **类型安全**:修改初始化值时需保持与原类型一致,例如`i32`类型变量应使用`ConstantInt`初始化 2. **调试信息**:通过`llvm::DebugInfo`接口同步更新调试元数据 3. **多线程安全**:修改局部静态变量时需注意其底层guard变量的同步机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值