I've just started to learn Cpp from the basics and am confused when I came across reference variables.
From what I have learn't reference variables are just like an alias (another name to the same memory), so in this case it need need any memory.
When I ran the below code:
class sample_class
{
public:
int num; //line1
int& refnum = num; //line2
}
int main()
{
sample_class sample_object;
cout<< "sample_class object size : " << sizeof(sample_object) <<endl;
return 0;
}
I got the output as:
sample_class object size : 8
==>Here, the size for num is 4 bytes (32-bit compiler) and refnum since a reference is simply an alias to num. Then, why in this case, the size of object is 8?
==>Also, if really an refnum is like an alias then when does this information (info that refnum also holds/alias to the same memory address of num) gets stored?
Edited :
And consider this case (changine the definition of sample_class):
class sample_class
{
public:
char letter; //line3
char& refletter = letter; //line4
char letter_two; //line5
}
Here, If I print the object size of sample_class object, I get it as 12 (though the size of letter,refletter and letter_two are each equal to 1). But if I comment line 4, the object size is just 2. How is this happening???
I'm interested to learn from the basics, so if I'm wrong anywhere please correct me
A reference is an alias, it should not be considered as a new variable. You cannot obtain its address and you cannot obtain its size. Any attempt to do so will instead obtain the address or size of the aliased object. In practice, most implementations implement them like pointers, but the standard does not require this. It makes no mention of the expected size for a reference.
From : http://en.cppreference.com/w/cpp/language/reference
References are not objects; they do not necessarily occupy storage, although the compiler may allocate storage if it is necessary to implement the desired semantics (e.g. a non-static data member of reference type usually increases the size of the class by the amount necessary to store a memory address).
Edit : The c++ standard gives a lot of leeway to implementations to decide for themselves the size of types and classes in order to accommodate the unique requirements of every architecture. In this case, padding is introduced between the members of your class. There is no requirement in c++ that a class's size must be equal to the sum of it's members' size. See Objects and alignment on cppreference.com for more information on the subject.
Edit 2 : There still seems to be some confusion regarding sizeof(T&).
From http://en.cppreference.com/w/cpp/language/sizeof :
When applied to a reference type, the result is the size of the referenced type.
The expression sizeof(T&) is treated as if you had written sizeof(T). This doesn't mean that the size of T& is equal to the size of T. It's simply that you cannot get the size of a reference directly with sizeof.
In addition to the answer already provided I would recommend having a read of the material regarding padding at:
Data structure padding
Which is a good basic discussion regarding padding of classes and structures in C++ with simple examples to consider.
A reference store the address of the variable it reference (like pointer with stronger pre/post-conditions).
This means that sizeof(T&) == sizeof(T*) == 4 on a 32bit architecture.
Comment from #FrançoisAndrieux about the real size of T&:
#nefas You state "This means that sizeof(T&) == sizeof(T*) == 4 on a 32bit architecture." but this is not true. Try it with a larger T. For example, sizeof(std::array<int, 10>&) is much larger than a pointer. You are taking the size of T, not T&.
An other thing that you have to take into account when calculating size of class/struct is the padding of the struct/class: the size of the struct maybe higher than the sum of the size of its member (I won't explain how the padding work because I haven't enough knowledge about it).
Related
In a legacy project I am maintaining in my freetime, operators delete/new and delete[]/new[] were overloaded to spot mismatches (new[] allocated object deleted and vice versa).
The original prefix had a length of 9 bytes. It has not led to issues since at least VS2010, and possibly even since VS6.
I have recently tackled rewriting this piece of code and to that end asked a question at codereview.stackexchange. The old prefixes had ended with one identical character which I removed, so my prefix was only 8 bytes long. Two people noted that this might break alignment and one referred to the C++ standard paragraph 6.11 Alignment... Unfortunately I fail to grasp the issue from reading it.
The second sentence there reads as follows:
An alignment is an implementation-defined integer value representing
the number of bytes between successive addresses at which a given object can be allocated.
... And as far as I understand this definition means that all is well:
If I allocate a single object, the OS has to handle distance to the previous and next objects in dynamic memory. Distance to the previous object will only increase by length of the prefix. Such distances are presumably not part of alignment. So, okay.
If I allocate an array, alignment between its elements has been handled before operator new[] gets its size parameter. I do not change this, so okay.
For begin and end of the array, considerations at 1) apply.
All seems to be perfectly fine. Yet, questions such as this one clearly signal that special alignment handling must be necessary in some cases.
What characterises these cases?
How can I generate such cases?
What invalidates my assumption that all is fine?
Or am I right and this is a perfectly harmless state of affairs?
Here is a code snippet illustrating the principle behind the overloads in question. Refer to my original question for a complete (and safer) example.
constexpr char PREFIX[] = "str1med";
constexpr std::size_t OFFSET = sizeof(PREFIX);
void * operator new(std::size_t size)
{
void * pointer = std::malloc(size + OFFSET);
std::memcpy(pointer, PREFIX, OFFSET);
return reinterpret_cast<std::byte*>(pointer) + OFFSET;
}
void operator delete(void * untypedPointer)
{
std::byte * pointer = reinterpret_cast<std::byte*>(untypedPointer);
pointer -= OFFSET;
assert(std::memcmp(pointer, prefix, OFFSET) == 0);
std::free(pointer);
}
You can generally infer the alignment requirement as being the largest power of two which is a factor of the requested size (this may be overly pessimistic at times). The occasions I'm aware of that require alignment better than 8 bytes on Windows are SIMD types and pointers used with FILE_FLAG_NO_BUFFERING.
Example:
auto required_alignment = size & (size ^ (size-1));
Unfortunately, the deallocator in most cases doesn't receive the size parameter, so you can't retrieve the offset using the same rule. However, if you encode the actual offset used during allocation in some fashion in the canary bytes immediately before the object, you can check the minimum size canary first, and from that recover the actual offset, original pointer, and check the full canary.
In your case, probably support for 16 byte alignment will suffice. Then you just need
auto align_16b = !(size & 0x0F);
and have a different canary for 16 byte aligned allocations. operator delete() then tests the preceding 8 bytes against both the 8 byte canary and the latter half of the 16 byte canary.
Important note: When the alignment requirement is greater than the alignment the underlying allocator provides, the offset may end up being different from the alignment. In this case operator delete() only needs to figure out the offset and doesn't care about the alignment requirement.
Check it out:
#define PREFIX "str1med" // canary size = 8
constexpr std::size_t OFFSET = sizeof(PREFIX);
void *operator new(std::size_t size) {
void * pointer = std::malloc(size + OFFSET);
std::memcpy(pointer, PREFIX, OFFSET);
return reinterpret_cast<char*>(pointer) + OFFSET;
}
void operator delete(void * untypedPointer) {
char * pointer = reinterpret_cast<char*>(untypedPointer);
pointer -= OFFSET;
assert(std::memcmp(pointer, PREFIX, OFFSET) == 0);
std::free(pointer);
}
int main() {
int *p = new int;
printf("%p\n", p);
}
This should print an 8-byte-aligned pointer value, which is suitably aligned for an int.
But now change the canary string value from "str1med" to, let's say, "str10med".
int main() {
int *p = new int;
printf("%p\n", p);
}
Now this prints a pointer value whose low-order four bits are 0x9. It's not suitably aligned for an int anymore!
The problem with your original code is that if a maintainer or refactorer changes the length of the string PREFIX, it breaks operator new. This is highly unexpected behavior to most programmers; we're not used to thinking of the length of a string as "significant" unless it's called out explicitly. You could mitigate this problem in your code by calling out the dependency explicitly:
constexpr std::size_t OFFSET = sizeof(PREFIX);
static_assert(OFFSET % 8 == 0, "preserve 8-byte alignment of new'ed chunks");
(This would also tell the reader that 16-byte alignment of new'ed chunks is explicitly not one of your goals.)
[Note: This answer references Standard draft n4659.]
Given the quote of $6.11 from the question:
An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated.
it is easy to find that, should alignment requirements be violated, the most basic concept of object lifetime becomes undefined: Under $6.8.1.1 we can read
[The lifetime of an object of type T begins when] storage with the proper alignment and size for type T is obtained, [...]
An incorrectly aligned object therefore never begins its lifetime. It then stands to reason that the following behaviours would be "okay" for a Standard-compliant compiler:
never calling the constructor
immediately calling the destructor
Nasal Demons
Thus, alignment must be correct for the program to be wellformed.
But when is alignment incorrect?
As seen in the question, one possible interpretation of $6.11 is the following:
Be A the alignment required of class C.
- then objects Oi of class C must have at least A bytes between them
- if Oi have exactly A between them, they form an array
Under this interpretation, adding a prefix of arbitrary size to the memory to be occupied by a single object is always okay. Similarly, adding such a prefix to the memory to be occupied by an array of objects is always okay.
There is however a more strict interpretation of the same paragraph:
Be A the alignment required of class C.
- then objects Oi of class C must have exactly a multiple of A between them
- if Oi have exactly A between them, they form an array
This interpretation would mean that alignment A describes exactly the memory adresses where an object O may reside via the term 0 + A * j = &Oi for i, j in N. Given that memory is virtual and even if allocated may or may not physically exist, this is at least somewhat surprising.
However, #Peter has commented that this is indeed the correct interpretation. It immediately follows, that prefixing the memory allocated for any object must ensure that A + sizeof(prefix) % A = 0 or else Nasal Demons.
[Note: This answer could be improved by providing a quote from the standard which supports the later interpretation. Sadly even after a token amount of scanning I have not yet been able to come up with a fitting passage.]
#include <iostream>
#include <cstring>
// This struct is not guaranteed to occupy contiguous storage
// in the sense of the C++ Object model (§1.8.5):
struct separated {
int i;
separated(int a, int b){i=a; i2=b;}
~separated(){i=i2=-1;} // nontrivial destructor --> not trivially copyable
private: int i2; // different access control --> not standard layout
};
int main() {
static_assert(not std::is_standard_layout<separated>::value,"sl");
static_assert(not std::is_trivial<separated>::value,"tr");
separated a[2]={{1,2},{3,4}};
std::memset(&a[0],0,sizeof(a[0]));
std::cout<<a[1].i;
// No guarantee that the previous line outputs 3.
}
// compiled with Debian clang version 3.5.0-10, C++14-standard
// (outputs 3)
What is the rationale behind weakening standard guarantees to the point that this program may show undefined behaviour?
The standard says:
"An object of array type contains a contiguously allocated non-empty set of N subobjects of type T." [dcl.array] §8.3.4.
If objects of type T do not occupy contiguous storage, how can an array of such objects do?
edit: removed possibly distracting explanatory text
1.
This is an instance of Occam's razor as adopted by the dragons that actually write compilers: Do not give more guarantees than needed to solve the problem, because otherwise your workload will double without compensation. Sophisticated classes adapted to fancy hardware or to historic hardware were part of the problem. (hinting by BaummitAugen and M.M)
2.
(contiguous=sharing a common border, next or together in sequence)
First, it is not that objects of type T either always or never occupy contiguous storage. There may be different memory layouts for the same type within a single binary.
[class.derived] §10 (8): A base class subobject might have a layout different from ...
This would be enough to lean back and be satisfied that what is happening on our computers does not contradict the standard. But let's amend the question. A better question would be:
Does the standard permit arrays of objects that do not occupy contiguous storage individually, while at the same time every two successive subobjects share a common border?
If so, this would influence heavily how char* arithmetic relates to T* arithmetic.
Depending on whether you understand the OP standard quote meaning that only the subobjects share a common border, or that also within each subobject, the bytes share a common border, you may arrive at different conclusions.
Assuming the first, you find that
'contiguously allocated' or 'stored contiguously' may simply mean &a[n]==&a[0] + n (§23.3.2.1), which is a statement about subobject addresses that would not imply that the array resides within a single sequence of contiguous bytes.
If you assume the stronger version, you may arrive at the 'element offset==sizeof(T)' conclusion brought forward in T* versus char* pointer arithmetic
That would also imply that one could force otherwise possibly non-contiguous objects into a contiguous layout by declaring them T t[1]; instead of T t;
Now how to resolve this mess? There is a fundamentally ambiguous definition of the sizeof() operator in the standard that seems to be a relict of the time when, at least per architecture, type roughly equaled layout, which is not the case any more. (How does placement new know which layout to create?)
When applied to a class, the result [of sizeof()] is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. [expr.sizeof] §5.3.3 (2)
But wait, the amount of required padding depends on the layout, and a single type may have more than one layout. So we're bound to add a grain of salt and take the minimum over all possible layouts, or do something equally arbitrary.
Finally, the array definition would benefit from a disambiguation in terms of char* arithmetic, in case this is the intended meaning. Otherwise, the answer to question 1 applies accordingly.
A few remarks related to now deleted answers and comments:
As is discussed in Can technically objects occupy non-contiguous bytes of storage?, non-contiguous objects actually exist. Furthermore, memseting a subobject naively may invalidate unrelated subobjects of the containing object, even for perfectly contiguous, trivially copyable objects:
#include <iostream>
#include <cstring>
struct A {
private: int a;
public: short i;
};
struct B : A {
short i;
};
int main()
{
static_assert(std::is_trivial<A>::value , "A not trivial.");
static_assert(not std::is_standard_layout<A>::value , "sl.");
static_assert(std::is_trivial<B>::value , "B not trivial.");
B object;
object.i=1;
std::cout<< object.B::i;
std::memset((void*)&(A&)object ,0,sizeof(A));
std::cout<<object.B::i;
}
// outputs 10 with g++/clang++, c++11, Debian 8, amd64
Therefore, it is conceivable that the memset in the question post might zero a[1].i, such that the program would output 0 instead of 3.
There are few occasions where one would use memset-like functions with C++-objects at all. (Normally, destructors of subobjects will fail blatantly if you do that.) But sometimes one wishes to scrub the contents of an 'almost-POD'-class in its destructor, and this might be the exception.
The answers I got for this question until now has two exactly the opposite kinds of answers: "it's safe" and "it's undefined behaviour". I decided to rewrite the question in whole to get some better clarifying answers, for me and for anyone who might arrive here via Google.
Also, I removed the C tag and now this question is C++ specific
I am making an 8-byte-aligned memory heap that will be used in my virtual machine. The most obvious approach that I can think of is by allocating an array of std::uint64_t.
std::unique_ptr<std::uint64_t[]> block(new std::uint64_t[100]);
Let's assume sizeof(float) == 4 and sizeof(double) == 8. I want to store a float and a double in block and print the value.
float* pf = reinterpret_cast<float*>(&block[0]);
double* pd = reinterpret_cast<double*>(&block[1]);
*pf = 1.1;
*pd = 2.2;
std::cout << *pf << std::endl;
std::cout << *pd << std::endl;
I'd also like to store a C-string saying "hello".
char* pc = reinterpret_cast<char*>(&block[2]);
std::strcpy(pc, "hello\n");
std::cout << pc;
Now I want to store "Hello, world!" which goes over 8 bytes, but I still can use 2 consecutive cells.
char* pc2 = reinterpret_cast<char*>(&block[3]);
std::strcpy(pc2, "Hello, world\n");
std::cout << pc2;
For integers, I don't need a reinterpret_cast.
block[5] = 1;
std::cout << block[5] << std::endl;
I'm allocating block as an array of std::uint64_t for the sole purpose of memory alignment. I also do not expect anything larger than 8 bytes by its own to be stored in there. The type of the block can be anything if the starting address is guaranteed to be 8-byte-aligned.
Some people already answered that what I'm doing is totally safe, but some others said that I'm definitely invoking undefined behaviour.
Am I writing correct code to do what I intend? If not, what is the appropriate way?
The global allocation functions
To allocate an arbitrary (untyped) block of memory, the global allocation functions (§3.7.4/2);
void* operator new(std::size_t);
void* operator new[](std::size_t);
Can be used to do this (§3.7.4.1/2).
§3.7.4.1/2
The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. There are no constraints on the contents of the allocated storage on return from the allocation function. The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified. The pointer returned shall be suitably aligned so that it can be converted to a pointer of any complete object type with a fundamental alignment requirement (3.11) and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function).
And 3.11 has this to say about a fundamental alignment requirement;
§3.11/2
A fundamental alignment is represented by an alignment less than or equal to the greatest alignment supported by the implementation in all contexts, which is equal to alignof(std::max_align_t).
Just to be sure on the requirement that the allocation functions must behave like this;
§3.7.4/3
Any allocation and/or deallocation functions defined in a C++ program, including the default versions in the library, shall conform to the semantics specified in 3.7.4.1 and 3.7.4.2.
Quotes from C++ WD n4527.
Assuming the 8-byte alignment is less than the fundamental alignment of the platform (and it looks like it is, but this can be verified on the target platform with static_assert(alignof(std::max_align_t) >= 8)) - you can use the global ::operator new to allocate the memory required. Once allocated, the memory can be segmented and used given the size and alignment requirements you have.
An alternative here is the std::aligned_storage and it would be able to give you memory aligned at whatever the requirement is.
typename std::aligned_storage<sizeof(T), alignof(T)>::type buffer[100];
From the question, I assume here that the both the size and alignment of T would be 8.
A sample of what the final memory block could look like is (basic RAII included);
struct DataBlock {
const std::size_t element_count;
static constexpr std::size_t element_size = 8;
void * data = nullptr;
explicit DataBlock(size_t elements) : element_count(elements)
{
data = ::operator new(elements * element_size);
}
~DataBlock()
{
::operator delete(data);
}
DataBlock(DataBlock&) = delete; // no copy
DataBlock& operator=(DataBlock&) = delete; // no assign
// probably shouldn't move either
DataBlock(DataBlock&&) = delete;
DataBlock& operator=(DataBlock&&) = delete;
template <class T>
T* get_location(std::size_t index)
{
// https://stackoverflow.com/a/6449951/3747990
// C++ WD n4527 3.9.2/4
void* t = reinterpret_cast<void*>(reinterpret_cast<unsigned char*>(data) + index*element_size);
// 5.2.9/13
return static_cast<T*>(t);
// C++ WD n4527 5.2.10/7 would allow this to be condensed
//T* t = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(data) + index*element_size);
//return t;
}
};
// ....
DataBlock block(100);
I've constructed more detailed examples of the DataBlock with suitable template construct and get functions etc., live demo here and here with further error checking etc..
A note on the aliasing
It does look like there are some aliasing issues in the original code (strictly speaking); you allocate memory of one type and cast it to another type.
It may probably work as you expect on your target platform, but you cannot rely on it. The most practical comment I've seen on this is;
"Undefined behaviour has the nasty result of usually doing what you think it should do, until it doesn’t” - hvd.
The code you have probably will work. I think it is better to use the appropriate global allocation functions and be sure that there is no undefined behaviour when allocating and using the memory you require.
Aliasing will still be applicable; once the memory is allocated - aliasing is applicable in how it is used. Once you have an arbitrary block of memory allocated (as above with the global allocation functions) and the lifetime of an object begins (§3.8/1) - aliasing rules apply.
What about std::allocator?
Whilst the std::allocator is for homogenous data containers and what your are looking for is akin to heterogeneous allocations, the implementation in your standard library (given the Allocator concept) offers some guidance on raw memory allocations and corresponding construction of the objects required.
Update for the new question:
The great news is there's a simple and easy solution to your real problem: Allocate the memory with new (unsigned char[size]). Memory allocated with new is guaranteed in the standard to be aligned in a way suitable for use as any type, and you can safely alias any type with char*.
The standard reference, 3.7.3.1/2, allocation functions:
The pointer returned shall be suitably aligned so that it can be
converted to a pointer of any complete object type and then used to
access the object or array in the storage allocated
Original answer for the original question:
At least in C++98/03 in 3.10/15 we have the following which pretty clearly makes it still undefined behavior (since you're accessing the value through a type that's not enumerated in the list of exceptions):
If a program attempts to access the stored value of an object through
an lvalue of other than one of the following types the behavior is
undefined):
— the dynamic type of the object,
— a cvqualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cvqualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cvqualified) base class type of the dynamic type of the object,
— a char or unsigned char type.
pc pf and pd are all different types that access memory specified in block as uint64_t, so for say 'pf the shared types are float and uint64_t.
One would violate the strict aliasing rule were once to write using one type and read using another since the compile could we reorder the operations thinking there is no shared access. This is not your case however, since the uint64_t array is only used for assignment, it is exactly the same as using alloca to allocate the memory.
Incidentally there is no issue with the strict aliasing rule when casting from any type to a char type and visa versa. This is a common pattern used for data serialization and deserialization.
A lot of discussion here and given some answers that are slightly wrong, but making up good points, I just try to summarize:
exactly following the text of the standard (no matter what version) ... yes, this is undefined behaviour. Note the standard doesn't even have the term strict aliasing -- just a set of rules to enforce it no matter what implementations could define.
understanding the reason behind the "strict aliasing" rule, it should work nicely on any implementation as long as neither float or double take more than 64 bits.
the standard won't guarantee you anything about the size of float or double (intentionally) and that's the reason why it is that restrictive in the first place.
you can get around all this by ensuring your "heap" is an allocated object (e.g. get it with malloc()) and access the aligned slots through char * and shifting your offset by 3 bits.
you still have to make sure that anything you store in such a slot won't take more than 64 bits. (that's the hard part when it comes to portability)
In a nutshell: your code should be safe on any "sane" implementation as long as size constraints aren't a problem (means: the answer to the question in your title is most likely no), BUT it's still undefined behaviour (means: the answer to your last paragraph is yes)
I'll make it short: All your code works with defined semantics if you allocate the block using
std::unique_ptr<char[], std::free>
mem(static_cast<char*>(std::malloc(800)));
Because
every type is allowed to alias with a char[] and
malloc() is guaranteed to return a block of memory sufficiently aligned for all types (except maybe SIMD ones).
We pass std::free as a custom deleter, because we used malloc(), not new[], so calling delete[], the default, would be undefined behaviour.
If you're a purist, you can also use operator new:
std::unique_ptr<char[]>
mem(static_cast<char*>(operator new[](800)));
Then we don't need a custom deleter. Or
std::unique_ptr<char[]> mem(new char[800]);
to avoid the static_cast from void* to char*. But operator new can be replaced by the user, so I'm always a bit wary of using it. OTOH; malloc cannot be replaced (only in platform-specific ways, such as LD_PRELOAD).
Yes, because the memory locations pointed to by pf could overlap depending on the size of float and double. If they didn't, then the results of reading *pd and *pf would be well defined but not the results of reading from block or pc.
The behavior of C++ and the CPU are distinct. Although the standard provides memory suitable for any object, the rules and optimizations imposed by the CPU make the alignment for any given object "undefined" - an array of short would reasonably be 2 byte aligned, but an array of a 3 byte structure may be 8 byte aligned. A union of all possible types can be created and used between your storage and the usage to ensure no alignment rules are broken.
union copyOut {
char Buffer[200]; // max string length
int16 shortVal;
int32 intVal;
int64 longIntVal;
float fltVal;
double doubleVal;
} copyTarget;
memcpy( copyTarget.Buffer, Block[n], sizeof( data ) ); // move from unaligned space into union
// use copyTarget member here.
If you tag this as C++ question,
(1) why use uint64_t[] but not std::vector?
(2) in term of memory management, your code lack of management logic, which should keep track of which blocks are in use and which are free and the tracking of contiguoous blocks, and of course the allocate and release block methods.
(3) the code shows an unsafe way of using memory. For example, the char* is not const and therefore the block can be potentially be written to and overwrite the next block(s). The reinterpret_cast is consider danger and should be abstract from the memory user logic.
(4) the code doesn't show the allocator logic. In C world, the malloc function is untyped and in C++ world, the operator new is typed. You should consider something like the new operator.
I have created a structure with a Id variable and a list of values. When I check the size before passing it to a function as a void* it is 12. Once it gets in the function it is 4. How do I keep the size the same?
Struct Code:
typedef struct
{
int id;
std::list<int> sensorValues;
}dataPacketDef;
Sending to other function code:
void SendDataBufferToServer()
{
cout << "Sending network data size: " << sizeof(dpd) << "\n";
client.writeData((void*)&dpd);
}
In the above function the size is 12 but in the receiving function the size is only 4. The struct is declared as a global variable for my main program.
In the function you are taking the size of a void*, not of a dataPacketDef. And, apparently, on your system sizeof(void*) is 4 bytes. The sizeof operator is simply type-driven: it does not do magical inspection of pointees. In fact, since you have performed type erasure with that antiquated cast, it can't.
You cannot serialise your object in this way. The list isn't flat data; its elements aren't actually part of the dataPacketDef, but dynamically allocated. The list holds pointers to those elements. All you're doing is serialising pointers, which become immediately meaningless on the target system.
Drop this approach entirely and look into how to properly serialise objects in modern C++.
before passing it to a function as a void* it is 12. Once it gets in the function it is 4
Because sizeof pointer in case of your program is 4 bytes, you cant read sizeof struct if you cast instance of your struct to void*. You might fix it by passing to your function also size of your struct ie foo(static_cast<void*>(&myStructObj), sizeof(TypeOfMyStruct)).
[edit]
also as Lightness Races in Orbit pointed out in his answer, serializing std::list is wrong, you will only write pointers to your file/
The size of a pointer is 4 bytes (or 8 if you were using an x64 compiler). If writeData needs to know the size of the pointed-to object, that information will need to be passed to it somehow; a common idiom is to pass the size as a second argument. With that done, you could use a helper function template to simplify things:
template<typename T>
void writeData(Client& client, const T& obj)
{
client.writeData((void*)&obj, sizeof(obj));
}
Although as molbdnilo noted in the question comments, client.writeData() probably still won't be able to do what you want it to due to the presence of a list in dataPacketDef.
This is correct.
A pointer always contains an address, which is 4 bytes on your machine.
Therefore a sizeof any pointer will be 4 bytes on a 32-bit addressing architecture.
sizeof(void*) is 4 bytes on a 32bit machine.
According to the standard, in [expr.sizeof] (5.3.3.2) we get:
When applied to a reference or a reference type, the result is the size of the referenced type.
This seems to go along with the fact that references are unspecified [dcl.ref] (8.3.2.4):
It is unspecified whether or not a reference requires storage
But it seems pretty strange to me to have this kind of inconsistency within the language. Regardless of whether or not the reference requires storage, wouldn't it be important to be able to determine how much size the reference uses? Seeing these results just seems wrong:
sizeof(vector<int>) == 24
sizeof(vector<int>*) == 8
sizeof(vector<int>&) == 24
sizeof(reference_wrapper<vector<int>>) == 8
What is the reasoning behind wanting sizeof(T&) == sizeof(T) by definition?
The choice is somewhat arbitrary, and trying to fully justify either option will lead to circular metaphysical arguments.
The intent of a reference is to be (an alias for) the object itself; under that reasoning it makes sense for them both to have the same size (and address), and that is what the language specifies.
The abstraction is leaky - sometimes a reference has its own storage, separate from the object - leading to anomolies like those you point out. But we have pointers for when we need to deal with a "reference" as a separate entity to the object.
Argument 1: A reference should be a synonym of your object hence the interface of the reference should be exactly the same as the interface of the object, also all operators should work in the same way on object and on reference (except type operators).
It will make sense in the following code:
MyClass a;
MyClass& b = a;
char a_buf[sizeof(a)];
char b_buf[sizeof(b)]; // you want b_buf be the same size as a_buf
memcpy(&a, a_buf, sizeof(a));
memcpy(&b, b_buf, sizeof(b)); // you want this line to work like the above line
Argument 2: From C++ standard's point of view references are something under the hood and it even doesn't say if they occupy memory or not, so it cannot say how to get their size.
How to get reference size: Since by all compilers references are implemented by help of constant pointers and they occupy memory, there is a way to know their size.
class A_ref
{A& ref;}
sizeof(A_ref);
It's not particularly important to know how much storage a reference requires, only the change in storage requirements caused by adding a reference. And that you can determine:
struct with
{
char c;
T& ref;
};
struct without
{
char c;
};
return sizeof (with) - sizeof (without);