What is the correct way to cast a struct to a type that matches its first set of members?

走远了吗. 提交于 2021-01-01 13:11:08

问题


Please consider the following structs from the WinAPI:

typedef struct _PROCESS_MEMORY_COUNTERS {
  DWORD  cb;
  DWORD  PageFaultCount;
  SIZE_T PeakWorkingSetSize;
  SIZE_T WorkingSetSize;
  SIZE_T QuotaPeakPagedPoolUsage;
  SIZE_T QuotaPagedPoolUsage;
  SIZE_T QuotaPeakNonPagedPoolUsage;
  SIZE_T QuotaNonPagedPoolUsage;
  SIZE_T PagefileUsage;
  SIZE_T PeakPagefileUsage;
} PROCESS_MEMORY_COUNTERS;

typedef struct _PROCESS_MEMORY_COUNTERS_EX {
  DWORD  cb;
  DWORD  PageFaultCount;
  SIZE_T PeakWorkingSetSize;
  SIZE_T WorkingSetSize;
  SIZE_T QuotaPeakPagedPoolUsage;
  SIZE_T QuotaPagedPoolUsage;
  SIZE_T QuotaPeakNonPagedPoolUsage;
  SIZE_T QuotaNonPagedPoolUsage;
  SIZE_T PagefileUsage;
  SIZE_T PeakPagefileUsage;
  SIZE_T PrivateUsage;
} PROCESS_MEMORY_COUNTERS_EX;

As you can see, PROCESS_MEMORY_COUNTERS_EX "inherits from" PROCESS_MEMORY_COUNTERS - the former starts with the exact definition of the latter, and adds an additional field.

Consider a function:

BOOL GetProcessMemoryInfo(
  HANDLE                   Process,
  PPROCESS_MEMORY_COUNTERS ppsmemCounters,
  DWORD                    cb
);

As you can see, it takes a PPROCESS_MEMORY_COUNTERS. However I would like to pass it a pointer to a PROCESS_MEMORY_COUNTERS_EX.

According to the standard, correct me if I'm wrong, I should be able to cast a PROCESS_MEMORY_COUNTERS_EX* to a PPROCESS_MEMORY_COUNTERS*, since the former starts with the exact definition of the latter.

In C it would be done like so:

PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(process, (PROCESS_MEMORY_COUNTERS*) &pmc, sizeof(pmc));

What is the correct way to do this in C++ code, if any? Should I use reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc), static_cast<PROCESS_MEMORY_COUNTERS*>(&pmc), or something else?


回答1:


Documentation states that GetProcessMemoryInfo accepts

A pointer to the PROCESS_MEMORY_COUNTERS or PROCESS_MEMORY_COUNTERS_EX structure that receives information about the memory usage of the process.

So, reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc) would be the answer to your question.

But you must set cb to the size of object being passed.




回答2:


As types are unrelated by inheritance rules, and none of them is void, you should use reinterpret_cast.

As explained by @eerorika, it will indeed be a violation of the non aliasing rule.

It will work anyway, with common compilers (and specifically with MSVC) because the types are trivially copyable. This would be perfectly legal:

PROCESS_MEMORY_COUNTERS_EX a = ...;  // initialized a object
PROCESS_MEMORY_COUNTERS b;           // uninitialized b object;

memcpy(&b, &a, sizeof(b));           // copy the REPRESENTATION

The representation is just a series of bytes so it is legal to copy it. The beginning of the representation of a is a valid representation for b, so you get a valid b object.

This is an intended usage for reinterpret_cast. We know that this does happen when dealing with legacy code, of when mixing C and C++ functions. So reinterpret_cast allow to use the representation of an object as if is was the representation of a different object. It cannot be anything other than UB on a standard point of view, because the representations of types are willingly not defined by the standard beyond some trivial cases.

But if you know that it will used on a specific architecture with a specific set of compilers, it will work.




回答3:


According to the standard, correct me if I'm wrong, I should be able to cast a B* to a A*

Cast, yes. This would be a reinterpret_cast. But depending on how the pointer is going to be used, it might not be practical. In particular, you couldn't access B through the pointer to A.

Casting the pointer back is one thing that can be done, so this would be well-defined:

void f(A* a) {
    B* b = reinterpret_cast<B*>(a);
    // ...
}

extern b* b;
f(reinterpret_cast<A*>(b));

Another thing that can be done with the reinterpreted pointer is to reinterpret it again as pointer to char and for example memcpy or memcmp the memory.


If you do need to access the "common initial sequence", then you could change the function. Because accessing common initial sequence of inactive standard layout struct member is an allowed special case of unions, this would work:

union AB {
    A a;
    B b;
};

void f(AB& ab) {
    // can access all members of ab.a
    // regardless of which member is active
} 

AB ab {
    .b = {},
};
f(ab);


来源:https://stackoverflow.com/questions/65196623/what-is-the-correct-way-to-cast-a-struct-to-a-type-that-matches-its-first-set-of

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!