C++ 전략을 작성하기 전에 몇 가지 기본 지식을 알아야 할 사항이 있습니다. 아래와 같은 자료를
만약 자기 자신을 프로그래머라고 말하는 사람이 메모리에 대해 아무것도 모르는 사람이라면, 나는 당신에게 말할 수 있다. 그는 분명히 과장하고 있다. C 또는 C++로 프로그램을 작성할 때, 메모리에 더 많은 관심을 기울여야 한다. 이것은 단순히 메모리의 합리적인 분배가 프로그램의 효율성과 성능에 직접적으로 영향을 미치기 때문만이 아니라, 더 중요한 것은 우리가 메모리를 조작할 때 무분별하게 문제가 발생할 수 있다는 것입니다. 그리고 많은 경우, 이러한 문제는 쉽게 발견되지 않습니다.
우리는 C++가 메모리를 세 가지 논리적 영역으로 나누는 것을 알고 있습니다. 스텝,
1 기본 개념
먼저
Type stack_object ;
stack_object은 주파수 객체로서, 그것의 생애는 정의점에서 시작되며, 그 함수가 반환될 때 그 생애는 종료된다.
또한, 거의 모든 임시 객체는
Type fun(Type object);
이 함수는 최소 두 개의 임시 객체를 생성한다. 첫째, 함수는 값에 의해 전달된다. 따라서 복사 구성 함수를 호출하면 임시 객체 object_copy1를 생성한다. 함수 내부에서 사용되는 것은 object_copy1이 아니라 object_copy1이다. 자연적으로, object_copy1은 함수가 반환될 때 풀리는
Type tt ,result ; //生成两个栈对象
tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
위의 두 번째 문장의 구현은 fun 함수가 반환할 때 임시 객체 object_copy2를 생성하고, 그 다음 부여 연산자를 호출하여 실행하는 것입니다.
tt = object_copy2 ; //调用赋值运算符
보시다시피, 컴파일러는 우리가 인지하지 못하는 상태에서 우리에게 많은 임시 객체를 생성하고, 이러한 임시 객체를 생성하는 데 시간과 공간의 비용이 많이 들 수 있습니다. 따라서, 당신은 아마도
다음으로, 스택을 살펴보자. 스택은 자유 저장장이라고도 불리는데, 그것은 프로그램이 실행되는 과정에서 동적으로 할당되어 있기 때문에 가장 큰 특징은 동성이다. C++에서 모든 스택 객체의 생성 및 파괴는 프로그래머가 책임지므로, 제대로 처리되지 않으면 메모리 문제가 발생할 수 있다. 스택 객체를 할당하고 있지만 풀기를 잊으면 메모리 유출이 발생합니다.
그렇다면, C++에서 스텝 객체를 어떻게 할당합니까? 유일한 방법은 new (물론, malloc 명령어도 C형 스텝 메모리를 얻을 수 있습니다) 를 사용하는 것입니다. new을 사용하면 스텝에 메모리를 할당하고 스텝 객체를 가리키는 지수를 반환합니다.
다시 정적 저장 구역을 살펴보자. 모든 정적 객체, 전지구적 객체는 정적 저장 구역에 배정되어 있다. 전지구적 객체에 대해서는, main() 함수가 실행되기 전에 배정되어 있다. 사실, main() 함수의 표시 코드가 실행되기 전에 컴파일러에서 생성된 main() 함수가 호출되고, main() 함수는 모든 전지구적 객체의 구성 및 초기화 작업을 수행하고, main (main) 함수가 종료되기 전에 컴파일러에서 생성된 exit 함수가 호출되어 모든 전지구적 객체를 풀어낸다. 예를 들어 다음 코드:
void main(void)
{
... // 显式代码
}
// 实际上转化为这样:
void main(void)
{
_main(); //隐式代码,由编译器产生,用以构造所有全局对象
... // 显式代码
...
exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
}
따라서, 이것을 알고 나면, 여기서 몇 가지 기법을 도출할 수 있다. 예를 들어, main() 함수가 실행되기 전에 어떤 준비 작업을 해야 한다고 가정하면, 우리는 이러한 준비 작업을 사용자 정의된 전 세계 객체의 구성 함수에 쓸 수 있다. 따라서, main() 함수의 엑스플렉스 코드 실행되기 전에, 이 전 세계 객체의 구성 함수가 호출되어 예상 동작을 수행하고, 우리의 목적을 달성한다.
또 다른 정적 객체는 클래스의 정적 구성원이다. 이 상황을 고려할 때 좀 더 복잡한 문제가 발생한다.
첫 번째 문제는 클래스의 정적 멤버 객체의 수명이며, 클래스의 정적 멤버 객체는 첫 번째 클래스 객체가 생성되면서 생성되고 전체 프로그램이 종료될 때 사라집니다. 즉, 어떤 경우가 있습니다. 프로그램에서 우리는 클래스를 정의하고, 클래스에 정적 객체가 멤버로 있지만, 프로그램 실행 중에, 만약 우리가 그 클래스 객체 중 하나를 만들지 않는다면, 그 클래스가 포함하는 정적 객체를 생성하지 않습니다.
두 번째 문제는 다음과 같은 상황이 발생했을 때입니다.
class Base
{
public:
static Type s_object ;
}
class Derived1 : public Base / / 公共继承
{
... // other data
}
class Derived2 : public Base / / 公共继承
{
... // other data
}
Base example ;
Derivde1 example1 ;
Derivde2 example2 ;
example.s_object = …… ;
example1.s_object = …… ;
example2.s_object = …… ;
위의 세 개의 blackbody 문장을 주목해 보세요. s_object이 동일한 객체를 방문한 것인지? 답은 예, 그들은 같은 객체를 indeed 가리키고 있습니다. 이것은 사실처럼 들리지 않습니다. 하지만 사실입니다. 당신은 직접 간단한 코드를 작성하여 확인할 수 있습니다.
만약 우리가 Derived1 타입의 객체를 Base 타입의 논리를 참고하지 않는 함수를 받아들이는 함수에게 전달할 때 절단이 발생한다고 생각해 봅시다. 그럼 절단은 어떻게 될까요? 이제 당신이 알고 있는 것처럼, 그것은 Derived1 타입의 객체에서 Subobject을 제거하고, Derived1의 모든 다른 사용자 정의 데이터 멤버들을 무시하고, 그 Subobject을 함수에게 전달하는 것입니다. (실제로, 함수에는 Subobject의 복사본이 사용됩니다.)
베이스 클래스를 계승하는 모든 파생 클래스의 객체는 베이스 타입의 서브 오브젝트를 포함하고 있습니다. (이것이 베이스 타입 지표로 파생1 객체의 키를 가리킬 수 있고, 자연적으로 다중형 키이기도 합니다.) 모든 서브 오브젝트와 모든 베이스 타입의 객체는 동일한 s_object 객체를 공유하고 있으며, 자연적으로, 베이스 타입에서 파생된 전체 계승 시스템 내의 클래스 인스턴스는 동일한 s_object 객체를 공유합니다. 위에서 언급한 예제, 예제1, 예제2의 객체 레이아웃은 다음과 같습니다.
2 세 가지 메모리 객체의 비교
스텝 객체는 생성 및 파괴 시점 모두 프로그래머에 의해 정의된다. 즉, 프로그래머는 스텝 객체의 삶에 대한 완전한 통제를 가지고 있다. 우리는 종종 이러한 객체를 필요로 한다. 예를 들어, 우리는 여러 함수들에 의해 접근할 수 있는 하나의 객체를 만들 필요가 있지만, 그것을 보편화하고 싶지 않다. 이 때 스텝 객체를 만드는 것이 의심할 여지 없이 좋은 선택이며, 그 다음 각 함수들 사이에 이 스텝 객체를 전달하여 그 객체의 공유를 실현할 수 있다. 또한,
그리고 다음에는 정적 객체를 살펴보겠습니다.
먼저, 범용 객체이다. 범용 객체는 클래스 간 통신과 함수 간 통신을 위한 가장 간단한 방법을 제공하지만, 이 방법은 우아하지 않다. 일반적으로, 완전히 객체 지향 언어에서는 범용 객체가 존재하지 않는다. 예를 들어 C#에서는 범용 객체가 안전하지 않고 높은 결합을 의미하기 때문에 범용 객체가 존재하지 않는다. 프로그램에서 범용 객체를 너무 많이 사용하는 것은 프로그램의 탄력성, 안정성, 유지보수성 및 복제성을 크게 감소시킨다.
다음으로 클래스의 정적 멤버가 있습니다. 앞서 언급한 바와 같이, 기본 클래스와 그 파생 클래스의 모든 객체는 이 정적 멤버 객체를 공유합니다. 따라서 이러한 클래스들 또는 이러한 클래스 객체들 사이에서 데이터 공유 또는 통신이 필요할 때, 이러한 정적 멤버는 의심할 여지없이 좋은 선택입니다.
그 다음에는 정적 로컬 객체들이 있는데, 주로 그 객체의 위치 함수가 반복적으로 호출되는 동안 중간 상태를 저장하는 데 사용된다. 그 중 가장 대표적인 예는 역수 함수이다. 우리는 역수 함수가 자신의 함수를 호출하는 것으로 알고 있다. 만약 역수 함수에서 비정형 로컬 객체를 정의한다면, 역수 함수 수가 상당히 많을 때 발생하는 과감도 크다.
회귀 함수 설계에서, 정적 객체는 비정형 로컬 객체를 대체할 수 있으며, 이는 회귀 호출과 반환에 따라 비정형 객체를 생성하고 방출하는 비용을 줄이는 것은 물론이고, 정적 객체는 회귀 호출의 중간 상태를 보존하고 각 호출 계층에 접근할 수 있다.
3
앞서 소개한 바와 같이,
4 덤불 객체를 생성하는 것을 금지합니다
앞서 언급한 바와 같이, 어떤 유형의 스텝 객체를 생성하는 것을 금지하기로 결정했을 때, 당신은 당신이 직접 생성할 수 있는 리소스 포장에 대한 클래스를 만들 수 있습니다. 이 유형의 객체는
그렇다면 스텝 객체를 생성하는 것을 금지하는 방법은 무엇입니까? 우리는 이미 알고 있습니다, 스텝 객체를 생성하는 유일한 방법은 new를 사용하는 것입니다. 만약 우리가 new를 사용하지 않는 경우 new는 작동하지 않습니다. 더 나아가서, new는 new를 실행할 때 new를 호출하고 new는 다시 로드 할 수 있습니다. 다른 방법은 new를 개인으로 설정하고, 대칭을 위해 operator를 개인으로 다시 로드하는 것이 좋습니다. 이제, 당신은 다시 의문을 가질 수 있습니다.
#include <stdlib.h> //需要用到C式内存分配函数
class Resource ; //代表需要被封装的资源类
class NoHashObject
{
private:
Resource* ptr ;//指向被封装的资源
... ... //其它数据成员
void* operator new(size_t size) //非严格实现,仅作示意之用
{
return malloc(size) ;
}
void operator delete(void* pp) //非严格实现,仅作示意之用
{
free(pp) ;
}
public:
NoHashObject()
{
//此处可以获得需要封装的资源,并让ptr指针指向该资源
ptr = new Resource() ;
}
~NoHashObject()
{
delete ptr ; //释放封装的资源
}
};
NoHashObject는 이제 다음과 같은 코드를 작성하면 스텝 객체를 금지하는 클래스입니다.
NoHashObject* fp = new NoHashObject (() ; // 컴파일 시기의 오류!
fp를 삭제합니다.
위의 코드는 컴파일 시기의 오류를 발생시킨다. 좋습니다, 이제 당신은 금지된 스텝 객체를 설계하는 방법을 알고 있습니다. 당신은 아마도 저와 같은 의문을 가지고 있습니다. 클래스의 정의가 변경될 수 없는 상태에서 그 유형의 스텝 객체를 생성할 수 없습니까?
void main(void)
{
char* temp = new char[sizeof(NoHashObject)] ;
//强制类型转换,现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp ;
temp = NULL ; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr ;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource() ;
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp ;//释放资源
temp = (char*)obj_ptr ;
obj_ptr = NULL ;//防止悬挂指针产生
delete [] temp ;//释放NoHashObject对象所占的堆空间。
}
위의 구현은 번거로웠고, 이 구현은 실제로 거의 사용되지 않지만, 그것을 이해하는 것은 C++ 메모리 객체를 이해하는 데 도움이 되기 때문에 방법을 썼습니다. 위의 많은 강제형 변환에 대해 가장 근본적인 것은 무엇입니까?
어떤 메모리의 데이터는 변하지 않으며, 타입은 우리가 착용하는 안경이고, 우리가 안경을 착용하면, 우리는 그에 상응하는 타입을 사용하여 메모리의 데이터를 해석합니다. 그래서 다른 해석은 다른 정보를 얻습니다.
강제형 변환은 실제로 다른 안경을 교체하고 같은 메모리의 데이터를 다시 보는 것입니다.
또한 다른 컴파일러가 객체의 멤버 데이터의 레이아웃을 다르게 배치할 수 있다는 것을 상기시켜야 합니다. 예를 들어, 대부분의 컴파일러는 NoHashObject의 ptr 포인터 멤버를 객체 공간의 첫 4 바이트에 배치하여 아래 문장의 변환 동작이 우리가 기대하는 것처럼 실행되도록 보장합니다.
리소스* rp = (리소스*) obj_ptr ;
그러나 모든 컴파일러가 그렇게 될 필요는 없습니다.
만약 우리가 어떤 종류의 스텝 객체를 생성하는 것을 금지할 수 있다면,
5
앞서 언급한 바와 같이,
그렇게 할 수 있고, 저도 그렇게 하려고 합니다. 하지만 그 전에, 한 가지 명확하게 생각해야 할 점은, 만약 우리가 구성 함수를 사적으로 설정한다면, 우리는 new를 사용하여 스텝 객체를 직접 생성할 수 없다는 것입니다. new은 객체에 공간을 할당한 후에 또한 그것의 구성 함수를 호출하기 때문입니다. 그래서, 저는 분석 함수를만 개인으로 설정하려고 합니다.
만약 클래스가 기본 클래스로 의도되지 않은 경우, 일반적인 방법은 그 해독 함수를 개인으로 선언하는 것이다.
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
다음으로, NoStackObject 클래스는 다음과 같습니다.
NoStackObject* hash_ptr = 새로운 NoStackObject (() ;
...... // hash_ptr로 지목된 객체에 대한 동작
hash_ptr->destroy (() ; 이 모든 것은 오, 조금 이상해 보이지는 않나요? 우리는 new로 객체를 만들 때 delete로 삭제하는 것이 아니라 destroy로 생성합니다. 분명히 사용자는 이런 이상한 방식에 익숙하지 않습니다. 그래서, 저는 구성 함수를 private 또는 protected로 설정하기로 결정했습니다. 이것은 다시 위에서 피하려고 시도했던 문제로 돌아갑니다. new을 사용하지 않고 어떤 방식으로 객체를 생성해야합니까? 우리는 간접적인 방법으로 수행 할 수 있습니다. 즉, 이것이 정적 멤버 함수를 제공하여 이러한 유형의 스텝 객체를 생성하는 데 특별히 사용하도록하십시오.
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
이제 NoStackObject 클래스는 다음과 같이 사용할 수 있습니다.
NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
...... // hash_ptr로 지목된 객체에 대한 동작
hash_ptr->destroy() ;
hash_ptr = NULL ; // 서핑 포인터를 사용하지 않도록
이제 더 나은 느낌이 들지 않습니까, 생성 대상과 해방 대상 작업이 일치합니다.
많은 C 또는 C++ 프로그래머들은 Junk Recycling에 대해, Junk Recycling이 스스로가 다이내믹 메모리를 관리하는 것보다 확실히 비효율적이라고 생각하며, 재활용할 때 반드시 프로그램을 멈추게 할 것이고, 만약 자신이 메모리 관리를 통제한다면, 할당과 방출 시간은 안정적이며, 프로그램이 멈추지 않을 것이라고 생각합니다. 마지막으로, 많은 C/C++ 프로그래머들은 C/C++에서 Junk Recycling 메커니즘을 구현할 수 없다고 확신합니다. 이러한 잘못된 견해들은 Junk Recycling 알고리즘을 이해하지 않기 때문에 추측됩니다.
사실 쓰레기 회수 메커니즘은 느리지는 않지만, 심지어는 동적 메모리 분배보다 더 효율적입니다. 우리는 분배를 할 수 있기 때문에, 분배 메모리를 할 때 새로운 메모리를 계속 쌓아 올리고, 이동 스파크의 지표가 충분합니다. 그리고 분배 프로세스는 생략되어 자연적으로 가속화되었습니다. 현대 쓰레기 회수 알고리즘은 많이 발전했으며, 증가 수집 알고리즘은 쓰레기 회수 프로세스를 단계적으로 수행하여 프로그램을 중단하지 않도록 할 수 있습니다.
그리고 쓰레기 재활용 알고리즘의 기초는 일반적으로 현재 사용할 수 있는 모든 메모리 블록을 스캔하고 표시하고, 이미 할당된 모든 메모리에서 표시되지 않은 메모리를 재활용하는 데 기반한다. C/C++에서 쓰레기 재활용을 실현할 수 없는 관점은 일반적으로 사용할 수 있는 모든 메모리 블록을 올바르게 스캔할 수 없다는 데 기반한다. 그러나, 불가능해 보이는 것은 실제로 실현되는 것이 복잡하지 않다. 첫째, 메모리를 스캔하는 데이터에 의해, 모기에 동적으로 할당된 메모리를 가리키는 지점이 쉽게 식별되며, 인식 오류가 있을 경우, 지표가 아닌 데이터를 지표로 지칭할 수 있을 뿐 아니라 지표가 아닌 데이터를 지표로 지칭할 수 있다. 따라서, 쓰레기 재활용 프로세스는 오류를 제거하지 않는 메모리 함수를 회수하는 것을 놓치게 된다.
쓰레기 처리를 할 때, bss 세그먼트, 데이터 세그먼트 및 현재 사용 중인
만약 당신이 당신의 프로젝트를 위해 좋은 쓰레기 처리기를 구현한다면, 메모리 관리 속도를 높이고, 심지어 전체 메모리 소모를 줄일 수도 있다. 만약 당신이 관심이 있다면, 온라인에서 이미 쓰레기 재활용에 관한 논문과 구현된 라이브러리를 검색할 수 있다.
번역주HK 장
#include<stdio.h>
int*fun(){
int k = 12;
return &k;
}
int main(){
int *p = fun();
printf("%d\n", *p);
getchar();
return 0;
}
이 접근은 접근할 수 있을 뿐만 아니라 수정할 수도 있습니다. 그러나 이러한 접근은 불확실합니다. 로컬 변수의 주소는 프로그램 자체의 스택에 있으며, 권한 변수가 종료된 후, 로컬 변수의 메모리 주소를 다른 변수에 부여하지 않는 한 그 값은 계속 존재합니다. 그러나 변경되면, 이 메모리 주소가 프로그램의 다른 변수에 부여될 수 있기 때문에, 포인터로 강제적으로 변경될 경우, 프로그램이 충돌할 수 있기 때문에, 비교적 위험합니다.