UFO ET IT

메모리 위치를 재사용하는 것이 안전합니까?

ufoet 2020. 11. 20. 17:25
반응형

메모리 위치를 재사용하는 것이 안전합니까?


이 질문은 C ++로 이식 된 기존 C 코드를 기반으로합니다. 나는 그것이 "안전한지"에 관심이 있습니다. 나는 이미 이렇게 쓰지 않았을 것임을 이미 알고 있습니다. 여기에있는 코드는 기본적으로 C ++가 아닌 C라는 것을 알고 있지만 C ++ 컴파일러로 컴파일되었으며 때때로 표준이 약간 다르다는 것을 알고 있습니다.

메모리를 할당하는 기능이 있습니다. 나는 반환 void*을에 캐스팅하고 int*그것을 사용하기 시작합니다.

나중에 나는 반환 void*을 a 로 캐스팅하고 그것을 Data*사용하기 시작합니다.

C ++에서 안전합니까?

예 :-

void* data = malloc(10000);

int* data_i = (int*)data;
*data_i = 123;
printf("%d\n", *data_i);

Data* data_d = (Data*)data;
data_d->value = 456;
printf("%d\n", data_d->value);

나는 저장된 것과 다른 유형을 통해 사용되는 변수를 읽지 않았지만 컴파일러가 그것을 볼 수 data_i있고 data_d다른 유형이므로 합법적으로 서로 별칭을 지정하고 내 코드를 재정렬 할 수 없다고 걱정합니다. 예를 들어 data_d첫 번째 printf. 그것은 모든 것을 깨뜨릴 것입니다.

그러나 이것은 항상 사용되는 패턴입니다. 를 삽입하는 경우 freemalloc는 영향을받는 메모리 자체를 건드리지 않고 동일한 데이터를 재사용 할 수있는 두 개의 액세스 사이에 나는 그것을 변경합니다 아무것도 생각하지 않는다한다.

내 코드가 손상되었거나 "올바른"것입니까?


"OK"입니다. 작성한대로 작동합니다 (프리미티브 및 POD (plain-old-datatypes) 가정). 그것은 안전하다. 사실상 사용자 지정 메모리 관리자입니다.

몇 가지 참고 사항 :

  • 중요하지 않은 소멸자가있는 개체가 할당 된 메모리 위치에 생성 된 경우 해당 개체가 호출되었는지 확인합니다.

    obj->~obj();
    
  • 객체를 생성 하는 경우 일반 캐스트에 대한 새로운 구문 배치를 고려하십시오 (POD에서도 작동)

    Object* obj = new (data) Object();
    
  • A에 대한 확인 nullptr(또는 NULL경우), malloc실패, NULL반환

  • 정렬은 문제가되지 않지만 메모리 관리자를 만들 때 항상이를 인식하고 정렬이 적절한 지 확인하십시오.

C ++ 컴파일러를 사용하고있는 경우 "C"특성을 코드에 유지하지 않으려면 전역 operator new().

그리고 항상 그렇듯이 일단 완료되면 free()(또는을 delete사용하는 경우 new)


아직 코드를 변환하지 않을 것이라고 언급했습니다. 당신이 그것을 고려 할 때 경우 또는, C의 몇 가지 관용적 기능은 오버 사용하실 수 있습니다 ++가 malloc심지어 세계를 ::operator new.

스마트 포인터를 std::unique_ptr<>보거나 std::shared_ptr<>메모리 관리 문제를 처리하도록 허용해야합니다.


의 정의에 따라 Data코드 손상 될 수 있습니다. 그건 나쁜 코드, 어느 쪽이든.

경우 Data일반 오래된 데이터 유형이 (POD, 즉 형식 정의 기본 유형, POD 유형 등의 구조체), 및 할당 된 메모리가 제대로 유형에 대한 정렬 (*), 다음 코드는 잘 정의 된 , 어떤 ( 사용하기 전에의 각 멤버 초기화*data_d 하는 한) "작동"을 의미 하지만 좋은 방법은 아닙니다. (아래 참조)

경우 Data비 POD 유형, 당신은 문제가 향하고 있습니다 : 포인터 할당은 예를 들어, 어떤 생성자를 호출하지 것이다. data_d"pointer to Data" 유형 인은 무언가를 가리 키기 때문에 사실상 거짓말을 할 수 있지만 그러한 유형이 생성 / 구성 / 초기화되지 않았기 때문에 무언가 유형이 아닙니다Data . 정의되지 않은 동작은 그 시점에서 멀지 않을 것입니다.

주어진 메모리 위치에서 객체 를 적절하게 구성 하는 솔루션을 새로운 배치 라고합니다 .

Data * data_d = new (data) Data();

이렇게하면 컴파일러가 해당 위치에서Data 개체 를 생성하도록 지시합니다 . 이것은 POD 및 비 -POD 유형 모두에서 작동합니다. 또한 메모리를 사용 하기 전에 소멸자 ( ) 를 호출하여 실행되는지 확인해야합니다 .datadata_d->~Data()delete

할당 / 해제 기능을 혼합하지 않도록주의하십시오. 당신이 무엇을 malloc()할 필요가 free()할당되어 무엇 거라고 new필요 delete하고 경우에 new []당신이 있습니다 delete []. 다른 조합은 UB입니다.


어쨌든 메모리 소유권에 "네이 키드"포인터를 사용하는 것은 C ++에서 권장되지 않습니다. 당신은

  1. new생성자와 delete클래스의 소멸자에 해당 객체를 넣어 객체를 메모리의 소유자로 만듭니다 (예 : 예외의 경우 객체가 범위를 벗어날 때 적절한 할당 해제 포함). 또는

  2. 위의 작업을 효과적으로 수행 하는 스마트 포인터사용 하십시오.


(*) : 구현은 "확장"유형을 정의하는 것으로 알려져 있으며, 그 정렬 요구 사항은 malloc ()에서 고려하지 않습니다. 언어 변호사들이 여전히 그들을 "POD"라고 부를지 모르겠습니다. 예를 들어, MSVC 는 malloc ()에서 8 바이트 정렬 을 수행하지만 SSE 확장 유형 __m12816 바이트 정렬 요구 사항 이있는 것으로 정의합니다 .


엄격한 앨리어싱을 둘러싼 규칙은 매우 까다로울 수 있습니다.

엄격한 앨리어싱의 예는 다음과 같습니다.

int a = 0;
float* f = reinterpret_cast<float*>(&a);
f = 0.3;
printf("%d", a);

이것은 다음과 같은 이유로 엄격한 앨리어싱 위반입니다.

  • 변수의 수명 (및 사용)이 겹칩니다.
  • 두 개의 다른 "렌즈"를 통해 동일한 메모리 조각을 해석합니다.

두 가지 를 동시에 수행 하지 않으면 코드가 엄격한 앨리어싱을 위반하지 않습니다.


C ++에서 개체의 수명은 생성자가 끝날 때 시작되고 소멸자가 시작될 때 중지됩니다.

기본 제공 유형 (소멸자 없음) 또는 POD (사소한 소멸자)의 경우 대신 메모리를 덮어 쓰거나 해제 할 때마다 수명이 종료됩니다.

참고 : 이것은 특히 메모리 관리자 쓰기를 지원하기위한 것입니다. 결국 mallocC operator new로 작성되고 C ++로 작성되며 명시 적으로 메모리를 풀링 할 수 있습니다.


I specifically used lenses instead of types because the rule is a bit more difficult.

C++ generally use nominal typing: if two types have a different name, they are different. If you access a value of dynamic type T as if it were a U, then you are violating aliasing.

There are a number of exceptions to this rule:

  • access by base class
  • in PODs, access as a pointer to the first attribute

And the most complicated rule is related to union where C++ shifts to structural typing: you can access a piece of memory through two different types, if you only access parts at the beginning of this piece of memory in which the two types share a common initial sequence.

§9.2/18 If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

Given:

  • struct A { int a; };
  • struct B: A { char c; double d; };
  • struct C { int a; char c; char* z; };

Within a union X { B b; C c; }; you can access x.b.a, x.b.c and x.c.a, x.c.c at the same time; however accessing x.b.d (respectively x.c.z) is a violation of aliasing if the currently stored type is not B (respectively not C).

Note: informally, structural typing is like mapping down the type to a tuple of its fields (flattening them).

Note: char* is specifically exempt from this rule, you can view any piece of memory through char*.


In your case, without the definition of Data I cannot say whether the "lenses" rule could be violated, however since you are:

  • overwriting memory with Data before accessing it through Data*
  • not accessing it through int* afterwards

then you are compliant with the lifetime rule, and thus there is no aliasing taking place as far as the language is concerned.


As long as the memory is used for only one thing at a time it's safe. You're basically use the allocated data as a union.

If you want to use the memory for instances of classes and not only simple C-style structures or data-types, you have to remember to do placement new to "allocate" the objects, as this will actually call the constructor of the object. The destructor you have to call explicitly when you're done with the object, you can't delete it.


As long as you only handle "C"-types, this would be ok. But as soon as you use C++ classes you will get into trouble with proper initialization. If we assume that Data would be std::string for example, the code would be very wrong.

The compiler cannot really move the store across the call to printf, because that is a visible side effect. The result has to be as if the side effects are produced in the order the program prescribes.


Effectively, you've implemented your own allocator on top of malloc/free that reuses a block in this case. That's perfectly safe. Allocator wrappers can certainly reuse blocks so long as the block is big enough and comes from a source that guarantees sufficient alignment (and malloc does).


As long a Data remains a POD this should be fine. Otherwise you would have to switch to placement new.

I would however put a static assert in place so that this doesn't change during later refactoring


I don't find any mistake in reusing the memory space. Only what I care for is the dangling reference. Reusing memory space as you have said I think it doesn't have any effect on the program.
You can go on with your programming. But it is always preferable to free() the space and then allocate to another variable.

참고URL : https://stackoverflow.com/questions/35333984/is-reusing-a-memory-location-safe

반응형