64-bit programming in front of the 32-bit world in the new era Author: Xie Qidong Source: topic network responsibility Editor: Ark
Write portable code for 16, 32, and 64-bit Architectures
Compared with 16-bit, 32-bit means faster programs, more memory and better processor architecture with direct addressing. In view of this, more and more programmers have begun to consider the huge advantage of using 64-bit processors.
Crre Research (cray research should be the brand name) computers have started to use 64-bit characters and have access to larger memory addresses. However, as part of our ongoing efforts to develop standard software and interact with other operating systems, we have stopped porting code that was originally based on a 32-bit processor. As a matter of fact, we are constantly encountering code that we call "32-bit". These codes are written on the assumption that the machine Character length is 32-bit, so it is difficult to transplant such code, therefore, some simple guidelines must be established to help compile code across 16, 32, and 64-bit processor platforms.
Due to some legacy issues, the C language seems a little surplus in terms of data types and data structures. Not only the char, short, Int, and long types can be used, but also the unsigned (unsigned) types. Of course, you can) in hybrid use, you can include the structure in the Union, and then include the Union in the structure. If the data type is not complex enough, it can be converted into bits, of course, you can also convert one data type to another data type you want. It is precisely because these tools are so powerful that they may hurt themselves if they are not used safely.
Advanced code structure
In the classical book "The Elements of programming style" by kernighan and plauger, their suggestion is "select a data representation that makes the program look simple ". For individuals, this means that advanced data structures are used for advanced programming, while low-level programming uses low-level data structures.
In our transplanted program, there is a common 32-bit bug. It is better to use it as an example. The routing protocol engine compiled by Cornell University for intercommunication between networks, in the Berkeley network environment (TCP/IP), inet_addr () is used to convert the string representing the Internet address into a binary format. The Internet address also happens to be 32-bit, just like other computers running the Berkeley network system.
However, there is also an advanced definition of Internet addresses: In _ ADDR structure. This structure defines the subdomain S _ ADDR, which is an unsigned long scalar containing Internet addresses. Inet_addr () accepts a pointer to a character and returns an unsigned long. If an error occurs during address String Conversion, inet_addr returns-1.
The program gated reads the configuration files that store Internet addresses in text format and puts them into sockaddr_in (this is an advanced structure containing the structure in_addr ). The code in Example 1 (a) can run normally on a 32-bit computer, but cannot run after being transplanted to a clay research computer. Why?
Example 1: Advanced code structure
()
Struct sockaddr_in saddrin Char * STR;If (saddrin. sin_addr.s_addr = inet_addr (STR) = (unsigned long)-1 ){ Do_some_error_handling; } |
(B)
Struct sockaddr_in saddrin Char * STR;If (inet_aton (STR, & saddrin. sin_addr )! = OK ){ Do_some_error_handling; } |
As long as inet_addr can parse strings correctly, everything is OK. When inet_addr returns an error on a 64-bit computer, this Code cannot be captured. You must consider comparing the data Bit Width in the statement to determine what is wrong.
First, inet_addr returns the error value -- unsigned long-1, which is expressed as 1 in 64 bits. This value is stored in the subdomain s_addr under the in_addr structure, in_addr must be 32-bit to match the Internet address, so it is a 32-bit unsigned int (in our compiler, Int Is 64-bit ). Now we store in 32 1 S, and the stored value will be compared with unsigned long-1. When we store 32 ones at unsigned int, the compiler automatically increases 32-bit to 64-bit. In this way, the values 0x00000000 ffffffff and 0 xffffffffff
Of course, the comparison of ffffffff fails. This is a hard-to-find bug, especially in the implicit increase from 32-bit to 64-bit.
What should we do with this bug? One solution is to compare 0 xffffffff to-1 in the statement, but this will make the code more dependent on objects of a specific size. Another method is to use an intermediate unsigned long variable to execute the comparison before saving the result to sockaddr_in, but this makes the program code more complex.
The real problem is that we expect an unsigned long value to be equal to a 32-bit value (such as an Internet address. The Internet address must be stored in 32-bit format, but sometimes it is very convenient to use a scalar to access a part of this address. In a 32-bit computer, a long value (often considered as 32-bit) is used to access this address. It seems that there is no problem. For the moment, if we do not want a low-level data item (32-bit Internet address) to be the same as a machine word, the advanced data type structure in_addr should be used all the time. Because in_addr does not have an invalid value, a separate state should be used as the return value.
The solution is to define a new function, just like inet_addr, but return a status value and accept a structure in_addr as the parameter. See example 1 (B ). Because advanced data elements can be used all the time, but the returned value does not overflow, this code can be transplanted across architectures, regardless of the length of the word. Although Berkeley released net2, which does define a new function inet_aton (), if you try to change the code in inet_addr (), it will corrupt many programs.
Low-level code structure
Low-level programming means directly manipulating the communication formats of physical devices or specific protocols. For example, device drivers often use very precise bit modes to manipulate control registers. In addition, when the network protocol transmits data items through a specific bit mode, it must also be translated as appropriate.
To manipulate physical data items, the data structure here must accurately reflect the manipulated content. Bit is a good choice, because they specify the bit digits and the arrangement. In fact, it is precisely this precision that enables bitwise to better map the physical structure (short, Int, and long will be changed because of different computers, compared with short, Int, and long, bits won't ).
When an image has a physical structure, bit is defined to achieve this precision, which requires you to always use an encoding style to access the structure. At this time, each bit field is named, and the code you write can directly access these bit fields. When accessing the physical structure, one thing you may not want to do is to use a scalar array (short, Int, or long ), the code for accessing these arrays assumes that a specific bit width exists. It may be incorrect when you port the code to a computer with different word widths.
A problem we encountered when porting the Pex image library involves the protocol message structure of the image. On a computer, if the length of the int type is the same as that of the elements in the message, the code in example 2 (A) will work normally. The 32-bit data element is no problem on a 32-character computer. When we get the 64-bit clay computer, it will go wrong. For example 2 (B), it is not only necessary to change the structure definition, but also all the Code involving the coord array. In this way, we have two options: rewrite all the Code involved in the message, or define a low-level structure and an advanced structure, then we use a special piece of code to copy data from one to the other, but I don't want to find every pair of zcoord
= Draw _ MSG. coord [2] references, and, when we need to port to a new architecture, rewrite all the code into Example 2 (c) it is undoubtedly a hard task. This special problem is caused by ignoring the differences in word lengths. Therefore, it cannot be assumed that machine lengths, short, Int, and long have the same size in the portable code.
Example 2: low-level code structure
()
Struct draw_msg { Int objectid; Int coord [3]; } |
(B)
Struct draw_msg { Int objectid: 32; Int coord1: 32; Int coord2: 32; Int coord3: 32; } |
(C)
Int * iptr, * optr, * limit;
Int XYZ [3];
Iptr = draw_msg.coord;
Limit = draw_msg.coord + sizeof (draw_msg.coord );
Optr = xyz;
While (iptr <limit)
* Optr ++ = * iptr ++;
Structure packaging and Word Alignment
It is precisely because the compiler will package the structure, so the change in the Word Length on different computers also leads to another problem. The C compiler alignment the length of a word on the boundary of a word. When data with a long character is followed by a small data, this method will generate a memory gap (however, there are exceptions, for example, when there is enough small data that can be filled with just one word ).
Some smart programmers usually have two or more structures in joint declaration, one of which is just filled with union, the other can be used to look at the Union from different perspectives. See example 3 (). Assume that the code is written by a 16-bit computer, where the int value is 16 bits and the long value is 32 bits. The code that accesses this structure will get a normal ing relationship (1 ), the code in Example 3 (B) also works as expected. However, if this code is transplanted to another computer with 32-bit characters, the ing relationship changes. If the compiler on the new computer allows you to use a 16-bit int, the word alignment will be shown in figure 2, or if the compiler follows the K & R conventions, int will be as long as a word (32 bits), and alignment is shown in 3. In any case, this will cause problems.
Example 3: Structure packaging and Word Alignment
()
Union parse_hdr { Struct HDR { Char data1; Char data2; Int data3; Int data4; } HDR; Struct tkn { Int class; Long tag; } Tkn; } Parse_item; |
(B)
Char * PTR = msgbuf; Parse_item.hdr.data1 = * PTR ++; Parse_item.hdr.data2 = * PTR ++; Parse_item.hdr.data3 = (* PTR ++ <8 | * PTR ++ ); Parse_item.hdr.data4 = (* PTR ++ <8 | * PTR ++ ); If (PARSE. tkn. Class >=min_token_class & Parse. tkn. Class <= max_token_class ){ Interpret_tag (PARSE. tkn. Tag ); } |
In the first case (Figure 2), the tag domain is not linearly stretched as expected, but is filled with garbage. In the second case (figure 3), both Class and tag fields do not make sense. Because the two char values are packaged into an int, they are no longer correct. Once again, we should not assume that the size of standard data types is the same, but also understand how they are mapped to other data types. This is the best way to write portable code.
Machine addressing features
Almost all processors are addressing words on the word boundary and are usually optimized. Some Other Processors allow other types of addressing, such as addressing in bytes or addressing in the unit of half word boundary, some processors even have auxiliary hardware that allows both word and half-word addressing on odd-number boundaries.
Addressing mechanisms may change on different computers. The fastest addressing mode is to address word boundaries. Other addressing methods require secondary hardware, which usually increases the clock cycle for memory access. The support of these excessive models and special hardware is contrary to the original design intention of the RISC processor. For the clay computer, it only supports addressing by words on the word boundary.
The compiler can provide some simulation on computers that do not provide multiple data-type addressing methods. For example, the compiler can generate some commands. When reading a word, the compiler can shift and shield it to find the desired position to simulate the half-word addressing in the word, however, this requires an additional clock period and the code size will be larger.
From this point of view, bit fields are very inefficient. When a word is extracted from a bit field, they produce the largest code volume. When you access other bit domains in the same word, you need to re-address the memory containing the bit domain word. This is a typical change of space for time.
When designing a data structure, we always want to use the minimum data type that can save the data to save space. Sometimes we are so stingy that we often use char and short to access bit-specific domains. This is like spending a dollar to save a dime. The efficiency of storage space is at the cost of hiding program speed and volume.
Imagine that you only allocated a small amount of space for a compact structure, but generated a lot of code to access the domain in the structure, and this Code is often executed, then, because of non-word addressing, code runs slowly, and a large amount of code is filled with domain extraction, so the program size increases. The space occupied by these extra code will let your previous efforts to save space go beyond the limit.
In the advanced data structure, specific bit positioning is no longer necessary. You should use words in all fields, rather than worrying about the space they occupy. Especially in some machine-dependent parts of the program, a typedef should be prepared for the word, as shown below:
/* On this computer, Int Is a word length */ Typedef word int; |
In the advanced structure, the following benefits apply to all domains:
A. Portability of other Computer Architectures
B. the compiler may generate the fastest code.
C. The processor may access the required memory as quickly as possible
D .. there is absolutely no unexpected structure alignment
It must also be admitted that in some cases, all words cannot be used. For example, there is a large structure, but it is not frequently accessed. If thousands of words are used, the size will increase by 25%, however, the use of words usually saves space, increases execution speed, and is more portable.
The following is our conclusion:
Writing cross-platform porting code is actually a simple task. The most basic rule is to hide as much detail as possible about the machine's font length and map the physical data structure with very precise data element bit sizes. Or, as previously suggested, advanced data structures are used for advanced programming, while low-level programming uses low-level data structures. When advanced data structures are clarified, the scalar type of standard C is, do not make any assumptions.