Now there is an iPhone coming with 64-bit architecture. long becomes 64 bits (while int remains 32-bit) , and everywhere NSInteger has been used is now a long and so 64-bits not 32. And twitter has quite a few people saying "I'm glad I've used NSInteger everywhere not int".
If you need to store a value that doesn't exceed 32 bits (for example in a loop which only loops 25 times), why should a long be used, because the 32 (at least) upper bits are going to be empty.
If the program has worked on 32-bit integers, then what benefit does using 64-bits for integers provide, when it uses up more memory?
Also there will be situations where using a 64-bit integer gives a different result to using a 32-bit integer. So if you use NSInteger then something may work on an iPhone 5S but not on an older device, whereas if int or long is explicitly used then the result will be the same on any device.
If you need to store a value that doesn't exceed 32 bits... why should a long be used?
If you can really make that guarantee, then there is absolutely no reason to value a 64 bit type over a 32 bit one. For simple operations like bounded loops, counters, and general arithmetic, 32-bit integers suffice. But for more complex operations, especially those required of high-performance applications - such as those that perform audio or image processing - the increase in the amount of data the processor can handle in 64 bit modes is significant.
If the program has worked on 32-bit integers, then what benefit does using 64-bits for integers provide, when it uses up more memory?
You make using more memory seem like a bad thing. By doubling the size of some data types, they can be addressed to more places in memory, and the more memory that can be addressed, the less time the OS spends loading code. In addition, having twice the amount of lanes for data in a processor bus equates to an order of magnitude more values that can be processed in a single go, and the increase in register size means an order of magnitude more data can be kept around in one register. This equates to, in simplest terms, a nearly automatic doubling of the speed of most applications.
Also there will be situations where using a 64-bit integer gives a different result to using a 32-bit integer? ...
Yes, but not in the way you'd think. 32-bit data types and operations as well as 64-bit operations (most simulated in software, or by special hardware or opcodes in 32-bit hosts) are "relatively stable" in terms of their sizes. You cannot make nearly as many guarantees on a 64-bit architecture because different compilers implement different versions of 64-bit data types (see LP64, SILP64, and LLP64). Practically, this means casting a 64 bit type to a 32-bit type - say a pointer to an int - is guaranteed to lead to information loss, but casting between two data types that are guaranteed to be 64 bits - a pointer and a long on LP64 - is acceptable. ARM is usually compiled using LP64 (all ints are 32-bit, all longs are 64-bit). Again, most developers should not be affected by the switch, but when you start dealing with arbitrarily large numbers that you try to store in integers, then precision becomes an issue.
For that reason, I'd recommend using NSUInteger and NSInteger in public interfaces, and APIs where there is no inherent bounds checking or overflow guards. For example, a TableView requests an NSUInteger amount of data not because it's worried about 32 and 64 bit data structures, but because it can make no guarantees about the architecture upon which it's compiled. Apple's attempt to make architecture-independent data types is actually a bit of a luxury, considering how little work you have to do to get your code to compile and "just work" in both architectures.
The internal storage for NSInteger can be one of many different backing types, which is why you can use it everywhere and do not need to worry about it, which is the whole point of it.
Apple takes care for backward compatibility if your app is running on a 32 or 64 bit engine and will convert your variable behind the scenes to a proper data type using the __LP64__ macro.
#if __LP64__
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
Related
Since memoryTypeBits in VkMemoryRequirements is a 32 bit uint, does it mean that there can be no more than 32 memory types?
Basically, yes. But you pretty much never see more than 12 on actual implementations. There just aren't that many combinations of heaps and memory allocation patterns.
At least, not yet. It's possible that extensions and later core features could balloon this past 32 (much like they had to extend the bits for stages to 64 when they added ray tracing). But thus far, they're pretty far from the limit.
I have heard that people use types such as NSInteger or CGFloat rather than int or float is because of something to do with 64bit systems. I still don't understand why that is necessary, even though I do it throughout my own code. Basically, why would I need a larger integer for a 64bit system?
People also say that it is not necessary in iOS at the moment, although it may be necessary in the future with 64bit iphones and such.
It is all explained here:
Introduction to 64-Bit Transition Guide
In the section Major 64-Bit Changes:
Data Type Size and Alignment
OS X uses two data models: ILP32 (in
which integers, long integers, and pointers are 32-bit quantities) and
LP64 (in which integers are 32-bit quantities, and long integers and
pointers are 64-bit quantities). Other types are equivalent to their
32-bit counterparts (except for size_t and a few others that are
defined based on the size of long integers or pointers).
While almost all UNIX and Linux implementations use LP64, other
operating systems use various data models. Windows, for example, uses
LLP64, in which long long variables and pointers are 64-bit
quantities, while long integers are 32-bit quantities. Cray, by
contrast, uses ILP64, in which int variables are also 64-bit
quantities.
In OS X, the default alignment used for data structure layout is
natural alignment (with a few exceptions noted below). Natural
alignment means that data elements within a structure are aligned at
intervals corresponding to the width of the underlying data type. For
example, an int variable, which is 4 bytes wide, would be aligned on a
4-byte boundary.
There is a lot more that you can read in this document. It is very well written. I strongly recommend it to you.
Essentially it boils down to this: If you use CGFloat/NSInteger/etc, Apple can make backwards-incompatible changes and you can mostly update your app by just recompiling your code. You really don't want to be going through your app, checking every use of int and double.
What backwards-incompatible changes? Plenty!
M68K to PowerPC
64-bit PowerPC
PowerPC to x86
x86-64 (I'm not sure if this came before or after iOS.)
iOS
CGFloat means "the floating-point type that CoreGraphics" uses: double on OS X and float on iOS. If you use CGFloat, your code will work on both platforms without unnecessarily losing performance (on iOS) or precision (on OS X).
NSInteger and NSUInteger are less clear-cut, but they're used approximately where you might use ssize_t or size_t in standard C. int or unsigned int simply isn't big enough on 64-bit OS X, where you might have a list with more than ~2 billion items. (The unsignedness doesn't increase it to 4 billion due to the way NSNotFound is defined.)
I just discovered that the ARM I'm writing code on (Cortex M0), doesn't support unaligned memory access.
Now in my code I use a lot of packed structures, and I never got any warnings or hardfaults, so how can the Cortex access members of these structures when it doesnt allow for unaligned access?
Compilers such as gcc understand about alignment and will issue the correct instructions to get around alignment issues. If you have a packed structure, you will have told the compiler about it so it know ahead of time how to do alignment.
Let's say you're on a 32 bit architecture but have a struct that is packed like this:
struct foo __attribute__((packed)) {
unsigned char bar;
int baz;
}
When an access to baz is made, it will do the memory loads on a 32 bit boundary, and shift all the bits into position.
In this case it will probably to a 32 bit load of the address of bar and a 32 bit load at the address of bar + 4. Then it will apply a sequence of logical operations such as shift and logical or/and to end up with the correct value of baz in a 32 bit register.
Have a look at the assembly output to see how this works. You'll notice that unaligned accesses will be less efficient than aligned accesses on these architectures.
On many older 8-bit microprocessors, there were instructions to load (and storing) registers which were larger than the width of the memory bus. Such an operation would be performed by loading half of the register from one address, and the other half from the next higher address. Even on systems where the memory bus is wider than 8 bits wide (say, 16 bits) it is often useful to regard memory as being an addressable collection of bytes. Loading a byte from any address will cause the processor to read half of a 16-bit memory location and ignore the other half. Reading a 16-bit value from an even address will cause the processor to read an entire 16-bit memory location and use the whole thing; the value will be the same as if one read two consecutive byte addresses and concatenated the result, but it will be read in one operation rather than two.
On some such systems, if one attempts to read a 16-bit value from an odd address, the processor will read two consecutive addresses, using half of one value and the other half of the other value, as though one had performed two single-byte reads and combined the results. This is called an unaligned memory access. On other systems, such an operation will result in a bus fault, which generally triggers some form of interrupt which may or may not be able to do something useful about it. Hardware to support unaligned accesses is rather complicated, and designing code to avoid unaligned accesses is generally not overly difficult. Thus, such hardware generally only exists either on processors that are already very complicated, or processors which will be running code that was designed for processors that would assembly multi-byte registers from single-byte reads (e.g. on the 8088, every 16-bit read required two 8-bit memory fetches, and a lot of 8088 code was run on later Intel processors).
I have a 64 bit system. I"m declaring my variables as 64 bit variables expecting my code to run faster. When I do functions such as 'String.IndexOf("J", X)', it fails because X is a Long but it is looking for a 32 bit value as the start index.
Is there any way to pass a 64 bit variable without converting it down to a 32 bit?
You got the wrong idea about 64-bit code. The arguments to the String.IndexOf() method do not change, the second argument is still an Integer. The only type in .NET that changes size is IntPtr.
This is all very much by design. A 64-bit processor does not execute code faster when you let it manipulate 64-bit integral values. Quite the contrary, it makes it run slower. Processor speed is throttled in large part by the size of the caches. The CPU caches are important because they help avoid the processor having to read or write data from RAM. Which is very slow compared to the speed of the processor. A worst-case processor stall from not having data in the L1, L2 or L3 caches can be 200 cycles.
The cache size is fixed. Using 64-bit variables makes the cache half as effective.
You also make your code slower by using Long and Option Strict Off. That requires the compiler to emit a conversion to cast the Long to an Integer. It is not visible in your code but it certainly is performed, you can see it when you look at the IL with ildasm.exe or a decompiler like ILSpy or Reflector.
Wouldn't it make more sense (for example) for an int to always be 4 bytes?
How do I ensure my C programs are cross platform if variable sizes are implementation specific?
The types' sizes aren't defined by c because c code needs to be able to compile on embedded systems as well as your average x86 processor and future processors.
You can include stdint.h and then use types like:
int32_t (32 bit integer type)
and
uint32_t (32 bit unsigned integer type)
C is often used to write low-level code that's specific to the CPU architecture it runs on. The size of an int, or of a pointer type, is supposed to map to the native types supported by the processor. On a 32-bit CPU, 32-bit ints make sense, but they won't fit on the smaller CPUs which were common in the early 1970s, or on the microcomputers which followed a decade later. Nowadays, if your processor has native support for 64-bit integer types, why would you want to be limited to 32-bit ints?
Of course, this makes writing truly portable programs more challenging, as you've suggested. The best way to ensure that you don't build accidental dependencies on a particular architecture's types into your programs is to port them to a variety of architectures early on, and to build and test them on all those architectures often. Over time, you'll become familiar with the portability issues, and you'll tend to write your code more carefully.
Becoming aware that not all processors have integer types the same width as their pointer types, or that not all computers use twos-complement arithmetic for signed integers, will help you to recognize these assumptions in your own code.
You need to check the size of int in your implementation. Don't assume it is always 4 bytes. Use
sizeof(int)