What is the difference between data types aside from length in memory? - c++

I've been trying to wrap my head around how C/C++ code is represented in machine code and I'm having trouble understanding what data types actually are apart from a designation of memory length.

Types also are associated with;
a set of values that all variables of that type can represent;
the layout in memory of that type (e.g. the meaning, if any, attached to each bit or byte that represents a variable),
the set of operations that can act on a variable;
the behaviour of those operations.
Types are not necessarily represented directly in machine code. A compiler emits a set of instructions and data (in a way that varies between target platforms) that manipulate memory and machine registers. The type of each variable, in C source, gives information to the compiler about what memory to allocate for it, and the compiler makes decisions for mapping between expressions (in C statements) and usage of registers and machine instructions to give the required effects.

Related

Relationship between physical registers and Intel SIMD variables?

What is the relationship between physical processor registers and the variables used in Intel intrinsics (e.g. __m128)?
A diagram explaining SIMD typically shows 2 registers but references on the Intel forums to "register pressure" and in this question to "register coloring" suggest there is more going on.
Can any number of variables representing registers be declared? How can this be when they're closely tied to a finite physical resource? What should one be aware of regarding how physical registers are chosen? What happens if more registers are declared than exist?
Can multiple pairs of registers be active at the same time?
Are there different types of physical registers?
The variable types like _m128, _m128i, _m128d, ... are there mainly to protect you. They ensure that you are not attempting to use standard operators like +, -, &, |, ==, ... and ensure that the compiler will throw an error if you are attempting to assign the wrong types. These types force the compiler to load themselves into the appropriate register (XMM* in this case), but still give the compiler the freedom to choose which one, or store them locally on the stack if all appropriate registers are taken. They also ensure that any time they are stored on the stack, they maintain the correct alignment (16 byte alignment in this case), so that intrinsic instructions that rely on alignment don't cause GPFs.
You may tightly tie one of these variables to a physical register if you like using the asm constructs:
__m128i myXMM1 asm( "%xmm1" );
But it's better to just let the compiler do its magic and choose the registers for you to allow better optimization.
Any number of these variables can be declared, and even overbooking your XMM register store might not result in using stack space, as long as your working set of registers remains small. Compiler scoping will usually realize when a value is no longer used and allow the optimizer to not store it back to the stack. Sometimes you can help the compiler out by creating your own scoped stack frame:
__m128i storedVar;
{
__m128i tempVar1, tempVar2, tempVar3;
// do some operations with tempVar1 -> 3
storedVar = tempVar1;
}
{
__m128i tempVar4, tempVar5, tempVar6, tempVar7, tempVar8;
// do some operations with tempVar4 -> 8
storedVar = tempVar4;
}
return storedVar;
Since the variables go out of scope at the closed curly brace, the compiler sees that the registers which used to contain those values are now freed up, so it doesn't need to exceed the total number of available XMM registers.
If you do overbook your register store, and all values need to be maintained, then the compiler will allocate the appropriate size on the stack and ensure that it is properly aligned, and the value of the XMM register will be swapped out to the stack to make room for a new value. Keep in mind that the stack space is well cached, so writes and reads there are not as harmful as you might have expected. The real hit that you take is the necessity of the extra move operations to swap them in and out.
There are different types of physical registers by width (64-bit, 128-bit, 256-bit, 512-bit), obviously associated with the corresponding C/C++ intrinsic data type. The different "flavors" for a given width ("__m128i", "__m128d", ...) can actually all reside in any of the registers of the given width. The type forces you to use the appropriate intrinsic type (_mm_and_si128 vs. _mm_and_pd, for example), which in turn generates an appropriate version of the instruction.
Something like the "and" is a good example because the resulting operation will be identical regardless of type - a bitwise "and". But using the wrong type can incur latency according to what I've read in the Intel docs. The integer instructions and floating point instructions have separate execution queues, and whenever data has to move from one execution queue to the other, there is a penalty. So generally it is good practice to choose the appropriate data type so that the appropriate instructions can be generated, and stay within the realm of that data type.

Curious to know how the memory part in c++ works [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
So already know there's like 'blocks' or units of memory called.. bytes? and different variables take up different amounts of bytes. But my real question is when you create a new program, say on the compiler, does the memory start storing at address one. And using a pointer you can see what fills what blocks of memory? Also is this ram? Sorry for so much wondering by trying to get a grasp on the lower level part of c++ to get a hint of how memory is stored and such, thanks.
Objects in C++ occupy memory, and if you can obtain the address of an object, you can inspect that memory. It's completely unspecified where and how that memory comes about; it's supposed to be provided by "the platform", i.e. the compiler knows how to generate machine code that interacts with the system's notion of memory in such a way that every object fits into some memory. You also have platform-provided services (malloc and operator new) to give you memory directly for your own use.
Since this question is likely to be closed fast (it fits in well with the original idea of SO, but not with current "policy") I'm adding this answer quickly so that I can continue writing it. I disagree that strongly with current policy, for this particular kind of case. So…
About the topic.
Memory management is an extremely large topic. However, your questions about it, e.g. “does the memory start storing at address one”, concern the very basics. And this is small topic, possible to answer.
The C++ memory model.
/ Bytes.
As seen from the inside of a C++ program, memory is a not necessarily contiguous sequence of bytes. A byte is in this context the smallest addressable unit of electronic memory (or more generally of computer main memory, if other technologies should become popular), and corresponds to C++ char. The C++11 standard describes it thusly, in its §1.7/1:
“A byte is at least large enough to contain
any member of the basic execution character set (2.3) and the eight-bit code units of the Unicode UTF-8
encoding form and is composed of a contiguous sequence of bits, the number of which is implementation-defined”
Essential facts about C++ bytes:
A byte is at least 8 bits.
In practice it’s either 8 bits or 16 bits. The latter size is used on some digital signal processors, e.g. from Texas Instruments.
The number of bits per byte is given by CHAR_BIT.
This macro symbol is defined by the <limits.h> C header. It yields a value that can be used at compile time. An alternative way to designate that value is std::numeric_limits<unsigned char>::digits, after including the <limits> C++ header.
unsigned char is commonly used as a byte type.
All three variants of char, namely plain char, unsigned char and signed char, are guaranteed to map to byte, but there is no dedicated standard C++ byte type.
/ Locations.
A value of a built-in type such as double typically occupies a small number of bytes, contiguous in memory. The C++ standard, in its §1.7/3, refers to that, the bytes of a basic value, as a memory location. The essential fact about locations is that two threads can update separate memory locations without interfering with each other, but this is not guaranteed if they update separate bytes in the same memory location.
The sizeof operator produces the number of bytes of a value of a specified type.
By definition, in C++11 in §5.3.3/1, sizeof(char) is 1.
/ Addresses.
To quote the C++11 standard’s §1.7/1, “Every byte has a unique address.”.
The standard doesn’t define address further, but in practice, on modern machines the addresses that a C++ program deals with are bitpatterns of a fixed size, typically 32 or 64 bits.
When a C++ program deals directly with addresses it must do so via pointers, which are adresses with associated types. As a special case the pointer type void* represents untyped addresses, and as such must be able to store the largest address bitpatterns. Thus, on a modern machine CHAR_BIT*sizeof(void*) is in practice the number of bits of an address as seen from inside a C++ program.
Pointer values (addresses) are only guaranteed comparable via the built-in ==, < etc. if they point within the same array, extended with a hypothetical extra item at the end. However, the standard library offers a more general pointer comparision. C++ §20.8.5/8:
“For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type
yield a total order, even if the built-in operators <, >, <=, >= do not.”
Thus depending on the machine addresses, as seen from C++, either are or can be mapped to integer values. But this does not mean that they can be mapped to int. Depending on the C++ implementation type int may be too small to hold addresses.
There are very few guarantees about what direction addresses increase in, e.g. that subsequent varaible declarations give you locations with increasing addresses. However, there is such a guarantee for non-static data members that (C++03) have no intervening access specifier or (C++11) have the same access, e.g. public. C++11 §9.2/14:
“Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so
that later members have higher addresses within a class object.”
There is also such a guarantee for items of an array.
The literal 0, used where a pointer value is expected, denotes the nullpointer of the relevant type. For the built in relational operators C++ supports comparing a non-0 pointer to 0 via == and !=, but does not support magnitude comparisons. For absolute safety pointer comparisons can be done via e.g. std::less etc., as noted above.
/ Objects.
An object is “a region of storage”, according to C++11 §1.8/1. That paragraph also notes that an object “has a type”, which determines how the bits in the memory region are interpreted. In order to create an object you can simply declare a variable (a variable is an object with a name) or e.g. use a new-expression.
Worth noting:
A region, in the formal sense of the C++ standard, is not necessarily contiguous.
As far as I can determine this fact is only implicit in the standard, in that an object can be a sub-object, which can be an object of a class with virtual inheritance (sharing a common base class sub-object), in a context of multiple inheritance, where that object – by definition a region of storage – is necessarily spread out in memory.
Dave Abrahams once contended that the intent was to support C++ implementations where objects could be spread around also in other situations than multiple virtual inheritance, but as far as I know no C++ implementations do that. In particular, a variable or any other most derived object (object that isn’t part of some other object) o is in practice a contiguous region of bytes, with all the bytes contained in the sizeof(o) bytes extending from and including the object’ start address.
/ Arrays.
An array, in the sense of array created via the [] notation, is contiguous sequence of objects of some fixed type T. Each item (object in the array) has an associated index, starting at 0 for the first item and contigously increasing. To refer to the first item of an array a, you can use square bracket notation and write a[0].
If the first item has start address a, then iten number n has start address a + n*sizeof(T).
In other words, addresses increase in the same direction as the item indices, with item 0 placed lowest in memory.
Operating system processes.
A C++ program can run on just about any kind of computer, from the smallest embedded chips to the larges supercomputers. In the small computer end of the scale there is not necessarily any operating system or memory management hardware, with the program accessing the computer’s physical memory and other hardware directly. But on e.g. a typical cell phone or desktop computer the program will be executed in an operating system process that isolates the program from direct access to the computer.
In particular, the addresses that an OS process see and manage, may not necessarily be physical memory addresses. Instead they may be just logical addresses, which transparently to your C++ code are very efficiently mapped to physical addresses. Among other things this allows you to run two or more instances of your program at the same time, without their memory addressing clashing – because the instances’ logical addresses are mapped to different parts of physical memory.
Practical note: as a security measure, unless otherwise specified a C++ program for Windows, created with Microsoft’s tools, will have have parts placed at different logical addresses in different instances, to make it more difficult for malware to exploit known locations. Thus you can’t even rely on fixed logical addresses. And so where objects will be placed, and so on, is not just compiler dependent and operating system dependent, but can depend on the particular instance of the program…
Still you have the guarantees discussed above, namely …
increasing addresses for sub-objects with the same access (e.g. public) within the same outer object, and
increasing addresses in the direction of higher indices in an array.
malloc and operator new are the library calls for allocating memory in C++ program. It is important to note that they aren't provided by the platform, they are provided by the standard library. All that is specified in C++ standard is that these calls should return a memory address that is allocated for the program code.
The platform usually have a different API for allocating memory from the OS, e.g. in Linux there are mmap() and brk() system calls, in Windows there is VirtualAlloc() system call. Malloc and operator new uses these system specific syscalls to request memory from the OS, and then suballocate them to the program. In the OS kernel itself, these system calls usually modifies MMU entries (on architectures that uses MMU).

range of values a c pointer can take?

In "Computer System: A Programmer's Perspective", section 2.1 (page 31), it says:
The value of a pointer in C is the virtual address of the first byte of some block of storage.
To me it sounds like the C pointer's value can take values from 0 to [size of virtual memory - 1]. Is that the case? If yes, I wonder if there is any mechanism that checks if all pointers in a program are assigned with legal values -- values at least 0 and at most [size of virtual memory - 1], and where such mechanism is built in -- in compiler? OS? or somewhere else?
There is no process that checks pointers for validity as use of invalid pointers has undefined effects anyway.
Usually it will be impossible for a pointer to hold a value outside of the addressable range as the two will have the same available range — e.g. both will be 32 bit. However some CPUs have rules about pointer alignment that may render some addresses invalid for some types of data. Some runtimes, such as 64-bit Objective-C, which is a strict superset of C, use incorrectly aligned pointers to disguise literal objects as objects on the heap.
There are also some cases where the complete address space is defined by the instruction set to be one thing but is implemented by that specific hardware to be another. An example from history is the original 68000 which defined a 32-bit space but had only 24 address lines. Very early versions of Mac OS used the spare 8 bits for flags describing the block of data, relying on the hardware to ignore them.
So:
there's no runtime checking of validity;
even if there were, the meaning of validity is often dependent on the specific model of CPU (not just the family) or specific version of the OS (ditto) so as to make checking a less trivial task than you might guess.
In practise what will normally happen if your address is illegal per that hardware but is accessed as though legal is a processor exception.
A pointer in C is an abstract object. The only guarantee provided by the C standard is that pointers can point to all the things they need to within C: functions, objects, one past the end of an object, and NULL.
In typical C implementations, pointers can point to any address in virtual memory, and some C implementations deliberately support this in large part. However, there are complications. For example, the value used for NULL may be difficult to use as an address, and converting pointers created for one type to another type may fail (due to alignment problems). Additionally, there are legal non-typical C implementations where pointers do not directly correlate to memory addresses in a normal way.
You should not expect to use pointers to access memory arbitrarily without understanding the rules of the C standard and of the C implementations you use.
There is no mechanism in C which will check if pointers in a program are valid. The programmer is responsible for using them correctly.
For practical purposes a C pointer is either NULL or a memory address to something else. I've never heard of NULL being anything but zero in real life. If it's a memory address you're not supposed to "care" what the actual number is; just pass it around, dereference it etc.

Do Google's Protocol Buffers automatically align data efficiently?

In a typical C or C++ struct the developer must explicitly order data members in a way that provides efficient memory alignment and padding, if that is an issue.
Google's Protocol Buffers behave a lot like structs and it is not clear how the compilation of these affects memory layout. Does anyone know if this tendency to organize data in a specific order for the sake of efficient memory layout is automatically handled by the protocol buffer compiler? I have been unable to find any information on this.
I.E. the buffer might actually internally order the data differently than it is specified in the message object of the protobuf.
In a typical C or C++ struct the developer must explicitly order data members in a way that provides efficient memory alignment and padding, if that is an issue.
Actually this is not entirely true.
It's true that most compilers (actually all I know of) tend to align struct elements to machine word addresses. They do this, due to performance reasons because it's usually cheaper to read from a word address and just mask away some bits than to read from the word address, shift the word, so the value you are looking for is right aligned and the mask away the bits not needed. (Of course this depends on the architecture you are compiling for)
So why is your statement I quoted above not true? - Because of the fact that compilers are arranging elements as described above, they also offer the programmer the opportunity to influnece this behavior. Usually this is done using a compiler specific pragma.
For example GCC and MS C Compilers provide a pragma called "pack" which allows the programmer to change the alignment behavior of the compiler for specific structs. Of course, if you choose to set pack to '1', the memory usage is improvide, but this will possibly impact your runtime behavior.
What never happens to my knowledge is a reordering of the members in a struct by the compiler.

equivalence statements in fortran

I am porting application from fortran to JAVA.I was wondering how to convert if equivalence is between two different datatypes.
If I type cast,i may loose the data or should I pass that as byte array?
You have to fully understand the old FORTRAN code. EQUIVALENCE shares memory WITHOUT converting the values between different datatypes. Perhaps the programmer was conserving memory by overlapping arrays that weren't used at the same time and the EQUIVALENCE can be ignored. Perhaps they were doing something very tricky, based on the binary representation of a particular platform, and you will need to figure out what they were doing.
There is extremely little reason to use EQUIVALENCE in modern Fortran. In most cases where bits need to be transferred from one type to another without conversion, the TRANSFER intrinsic function should be used instead.
From http://www.fortran.com/F77_std/rjcnf0001-sh-8.html#sh-8.2 :
An EQUIVALENCE statement is used to specify the sharing of storage units by two or more entities in a program unit. This causes association of the entities that share the storage units.
If the equivalenced entities are of different data types, the EQUIVALENCE statement does not cause type conversion or imply mathematical equivalence. If a variable and an array are equivalenced, the variable does not have array properties and the array does not have the properties of a variable.
So, consider the reason it was EQUIVALENCE'd in the Fortran code and decide from there how to proceed. There's not enough information in your question to assess the intention or best way to convert it.