35

When I learned the C++ language for the first time I learned that besides int, float etc, smaller or bigger versions of these data types existed within the language. For example I could call a variable x

int x;
or 
short int x;

The main difference being that short int takes 2 bytes of memory while int takes 4 bytes, and short int has a lesser value, but we could also call this to make it even smaller:

int x;
short int x;
unsigned short int x;

which is even more restrictive.

My question here is if it's a good practice to use separate data types according to what values your variable take within the program. Is it a good idea to always declare variables according to these data types?

10
  • 3
    are you aware of Flyweight design pattern? "an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory..."
    – gnat
    Commented Apr 17, 2012 at 7:44
  • 5
    With standard packing/alignment compiler settings, the variables will be aligned to 4 byte boundaries anyway, so there might not by any difference at all.
    – nikie
    Commented Apr 17, 2012 at 8:36
  • 37
    Classic case of premature optimization.
    – scarfridge
    Commented Apr 17, 2012 at 9:05
  • 1
    @nikie - they might be aligned on a 4 byte boundary on an x86 processor but this is not true in general. MSP430 places char on any byte address and everything else on an even byte address. I think that AVR-32 and ARM Cortex-M are the same.
    – uɐɪ
    Commented Apr 17, 2012 at 14:13
  • 3
    The 2nd part of your question implies that adding unsigned somehow makes an integer occupy less space, which is of course false. It'll have the same count of discrete representable values (give or take 1 depending on how sign is represented) but just shifted exclusively into the positive. Commented Sep 6, 2016 at 21:49

8 Answers 8

44

Most of the time the space cost is negligible and you shouldn't worry about it, however you should worry about the extra information you are giving by declaring a type. For example, if you:

unsigned int salary;

You are giving a useful piece of information to another developer: salary cannot be negative.

The difference between short, int, long is rarely going to cause space problems in your application. You are more likely to accidentally make the false assumption that a number will always fit in some datatype. It's probably safer to always use int unless you are 100% sure your numbers will always be very small. Even then, it is unlikely to save you any noticeable amount of space.

14
  • 5
    Its true it is rarely going to cause problems these days, but if you are designing a library or a class that another developer will use, well that's another matter. Maybe they will need storage for a million of these objects, in which case the difference is large - 4MB compared to 2MB just for this one field. Commented Apr 17, 2012 at 7:40
  • 31
    Using unsigned in this case is a bad idea: not only the salary cannot be negative, but the difference between two salaries cannot be negative either. (In general, using unsigned for anything but bit-twiddling and having defined behavior on overflow is a bad idea.)
    – zvrba
    Commented Apr 17, 2012 at 9:01
  • 17
    @zvrba: The difference between two salaries is not itself a salary and so it is legitimate to use a different type that is signed.
    – JeremyP
    Commented Apr 17, 2012 at 16:03
  • 13
    @JeremyP Yes but if you're using C (and it looks like this is true in C++ too), unsigned integer subtraction results in an unsigned int, which cannot be negative. It might turn into the right value if you cast it to a signed int, but the result of the computation is an unsigned int. See also this answer for more signed/unsigned computation weirdness - which is why you should never use unsigned variables unless you're really twiddling bits.
    – Tacroy
    Commented Apr 17, 2012 at 16:15
  • 5
    @zvrba: The difference is a monetary quantity but not a salary. Now you could argue that a salary is also a monetary quantity (constrained to positive numbers and 0 by validating the input which is what most people would do) but the difference between two salaries is not itself a salary.
    – JeremyP
    Commented Apr 18, 2012 at 8:43
31

The OP said nothing about the type of system they are writing programs for, but I assume the OP was thinking of a typical PC with GB's of memory since C++ is mentioned. As one of the comments says, even with that kind of memory, if you have several million items of one type -- such as an array -- then the size of the variable can make a difference.

If you get into the world of embedded systems -- which is not really outside the scope of the question, since the OP doesn't limit it to PCs -- then the size of data types very much matters. I just finished a quick project on a 8-bit microcontroller which has only 8K words of program memory and 368 bytes of RAM. There, obviously every byte counts. One never uses a variable bigger than they need (both from a space standpoint, and code size -- 8-bit processors use a lot of instructions to manipulate 16 and 32-bit data). Why use a CPU with such limited resources? In large quantities, they can cost as little as a quarter.

I'm currently doing another embedded project with a 32-bit MIPS-based microcontroller which has 512K bytes of flash and 128K bytes of RAM (and costs around $6 in quantity). As with a PC, the "natural" data size is 32-bits. Now it becomes more efficient, code-wise, to use ints for most variables instead of chars or shorts. But once again, any type of array or structure must be considered whether smaller data types are warranted. Unlike compilers for larger systems, it is more likely variables in a structure will be packed on an embedded system. I take care to always try to put all 32-bit variables first, then 16-bit, then 8-bit to avoid any "holes".

1
  • 10
    +1 for the fact that different rules apply to embedded systems. The fact that C++ is mentioned does not mean that the target is a PC. One of my recent projects was written in C++ on a processor with 32k of RAM and 256K of Flash.
    – uɐɪ
    Commented Apr 17, 2012 at 14:03
14

The answer depends on your system. Generally, here are the advantages and disadvantages of using smaller types:

Advantages

  • Smaller types use less memory on most systems.
  • Smaller types gives faster calculations on some systems. Particularly true for float vs double on many systems. And smaller int types also give significantly faster code on 8- or 16-bit CPUs.

Disadvantages

  • Many CPUs have alignment requirements. Some access aligned data faster than unaligned. Some must have the data aligned to even be able to access it. The larger integer types equal one aligned unit, so they are most likely not misaligned. This means that the compiler might be forced to put your smaller integers in larger ones. And if the smaller types are part of a larger struct, you may get various padding bytes silently inserted anywhere in the struct by the compiler, to fix alignment.
  • Dangerous implicit conversions. C and C++ have several obscure, dangerous rules for how variables are promoted to larger ones, implicitly without a typecast. There are two sets of implicit conversion rules entwined with each other, called the "integer promotion rules" and the "usual arithmetic conversions." Read more about them here. These rules are one of the most common causes for bugs in C and C++. You can avoid a whole lot of problems by simply using the same integer type all over the program.

My advise is to like this:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

Alternatively, you can use the int_leastn_t or int_fastn_t from stdint.h, where the n is the number 8, 16, 32 or 64. int_leastn_t type means "I want this to be at least n bytes but I don't care if the compiler allocates it as a larger type to suit alignment".

int_fastn_t means "I want this to be n bytes long, but if it will make my code will run faster, the compiler should use a larger type than specified".

Generally, the various stdint.h types are much better practice than plain int etc, because they are portable. The intention with int was to not give it a specified width solely to make it portable. But in reality, it is hard to port because you never know how large it will be on a specific system.

1
  • Spot on regarding alignment. In my current project, gratuitous use of uint8_t on a 16-bit MSP430 crashed the MCU in mysterious ways (most likely misaligned access happened somewhere, perhaps GCC's fault, perhaps not) -- merely replacing all uint8_t with 'unsigned' eliminated the crashes. Use of 8-bit types on >8-bit archs if not fatal is at least inefficient: compiler generates additional 'and reg, 0xff' instructions. Use 'int/unsigned' for portability and free the compiler from extra constraints.
    – alexei
    Commented Jul 16, 2015 at 22:07
11

Depending on how the specific operating system works, you generally expect the memory to be allocated unoptimized such that when you call for a byte, or a word or some other small data type to be allocated, the value occupies an entire register all of it's very own. How your compiler or interpreter works to interpret this however is something else, so if you were to compile a program in C# for instance, the value might physically occupy a register for itself, however the value will be boundary checked to ensure you don't try to store a value that will exceed the bounds of the intended datatype.

Performance-wise, and if you are really pedantic about such things, it's likely faster to simply use the datatype that most closely matches the target register size, but then you miss out on all of that lovely syntactic sugar that makes working with variables so easy.

How does this help you? Well, it's really up to you to decide what sort of situation you are coding for. For nearly every program I have ever written, it's enough to simply trust your compiler to optimize things and to use the datatype that is most useful to you. If you need high precision, use the larger floating point data types. If working with only positive values, you can probably use an unsigned integer, but for the most part, simply using the int datatype is enough.

If however you have some very strict data requirements, such as writing a communications protocol, or some sort of encryption algorithm, then using range-checked datatypes can come in very handy, particularly if you are trying to avoid problems relating to data overruns/underruns, or invalid data values.

The only other reason I can think of off the top of my head to use specific datatypes is when you are trying to communicate intent within your code. If you use a shortint for example, you are telling other developers that you are allowing positive and negative numbers within a very small value range.

6

As scarfridge commented, this is a

Classic case of premature optimization.

Trying to optimise for memory usage might impact other areas of performance, and the golden rules of optimisation are:

The First Rule of Program Optimisation: Don't do it.

The Second Rule of Program Optimisation (for experts only!): Don't do it yet."

— Michael A. Jackson

In order to know whether now is the time to optimise requires benchmarking and testing. You need to know where your code is being inefficient, so that you can target your optimisations.

In order to determine whether the optimised version of the code is actually better than the naive implementation at any given time, you need to benchmark them side-by-side with the same data.

Also, remember that just because a given implementation is more efficient on the current generation of CPU's, doesn't mean it will always be so. My answer to the question Is micro-optimisation important when coding? details an example from personal experience where an obsolete optimisation resulted in an order of magnitude slowdown.

On many processors, unaligned memory accesses are significantly more costly than aligned memory accesses. Packing a couple of shorts into your struct may just mean that your program has to perform pack/unpack operation every time you touch either value.

For this reason, modern compilers ignore your suggestions. As nikie comments:

With standard packing/alignment compiler settings, the variables will be aligned to 4 byte boundaries anyway, so there might not by any difference at all.

Second guess your compiler at your peril.

There is a place for such optimisations, when working with terabyte datasets or embedded micro-controllers, but for most of us, it isn't really a concern.

3

The main difference being that short int takes 2 bytes of memory while int takes 4 bytes, and short int has a lesser value, but we could also call this to make it even smaller:

This is incorrect. You can't make assumptions about how many bytes each type holds, other than char being one byte and at least 8 bits per byte, along with each type's size being greater than or equal to the previous.

The performance benefits are incredibly minuscule for stack variables - they'll likely be aligned/padded anyway.

Because of this, short and long have practically no use nowadays, and you're almost always better off using int.


Of course, there's also stdint.h which is perfectly fine to use when int doesn't cut it. If you're ever allocating huge arrays of integers/structs then an intX_t makes sense as you can be efficient and rely on the size of the type. This is not at all premature as you can save megabytes of memory.

5
  • 1
    Actually, with the advent of 64 bit environments, long may be different to int. If your compiler is LP64, int is 32 bits and long is 64 bits and you'll find that ints may still be 4 byte aligned (my compiler does, for instance).
    – JeremyP
    Commented Apr 17, 2012 at 16:11
  • 1
    @JeremyP Yeah, did I say otherwise or something?
    – Pubby
    Commented Apr 17, 2012 at 17:27
  • Your last sentence which claims short and long have practically no use. Long certainly does have a use, if only as the base type of int64_t
    – JeremyP
    Commented Apr 18, 2012 at 9:10
  • @JeremyP: You can live just fine with int and long long.
    – gnasher729
    Commented Jul 17, 2016 at 13:46
  • @gnasher729: What do you use if you need a variable that can hold values more than 65 thousand, but never as much as a billion? int32_t, int_fast32_t, and long are all good options, long long is just wasteful, and int non-portable.
    – Ben Voigt
    Commented Dec 9, 2018 at 1:16
3

This will be from a kind of OOP and/or enterprisey/application point of view and might not be applicable in certain fields/domains, but I kind of wanna bring up the concept of primitive obsession.

It IS a good idea to use different data types for different kinds of information in your application. However, it's probably NOT good idea to use the built-in types for this, unless you have some serious performance issues (that have been measured and verified and so on).

If we want to model temperatures in Kelvin in our application, we COULD use a ushort or uint or something similar to denote that "the notion of negative degrees Kelvin is absurd and a domain logic error". The idea behind this is sound, but you're not going all the way. What we realized is that we can't have negative values, so it's handy if we can get the compiler to make sure nobody assigns a negative value to a Kelvin temperature. It's ALSO true that you can't do bitwise operations on temperatures. And you can't add a measure of weight (kg) to a temperature (K). But if you model both temperature and mass as uints, we can do just that.

Using built-in types to model our DOMAIN entities is bound to lead to some messy code and some missed checks and broken invariants. Even if a type captures SOME part of the entity (can't be negative), it's bound to miss others (can't be used in arbitrary arithmetic expressions, can't be treated as an array of bits, etc.)

The solution is to define new types that encapsulates the invariants. This way you can make sure that money is money and distances are distances, and you can't add them together, and you can't create a negative distance, but you CAN create a negative amount of money (or a debt). Of course, these types will use the built-in types internally, but this is hidden from clients. Relating to your question about performance/memory consumption, this sort of thing can allow you to change how things are stored internally without changing the interface of your functions that operate on your domain entities, should you find out that damn, a short is just too damn large.

1

Yes, of course. It is good idea to use uint_least8_t for dictionaries, huge constants arrays, buffers etc. It is better to use uint_fast8_t for processing purposes.

uint8_least_t (storage) -> uint8_fast_t (processing) -> uint8_least_t (storage).

For example you are taking 8 bits symbol from source, 16 bit codes from dictionaries and some 32 bit constants. Than you are processing 10-15 bit operations with them and outputs 8 bit destination.

Let's imagine that you have to process 2 gigabytes of source. Amount of bit operations is huge. You will receive great perfomance bonus if you will switch to fast types during processing. Fast types can be different for each CPU family. You can include stdint.h and use uint_fast8_t, uint_fast16_t, uint_fast32_t, etc.

You could use uint_least8_t instead of uint8_t for portability. But nobody actually knows what modern cpu will use this feature. VAC machine is a museum piece. So maybe it is an overkill.

1
  • 1
    While you may have a point with the data types you listed, you should explain why they are better rather than just stating that the are. For people like me who are unfamiliar with those data types, I had to google them in order to understand what you are talking about.
    – Peter M
    Commented Dec 8, 2018 at 12:25

Not the answer you're looking for? Browse other questions tagged or ask your own question.