[Knowledge Sharing] C Language Application-Error-prone Chapter

1. Introduction to C language

    C language has a simple structure, high efficiency and portability, so it is widely used. But looking at its historical standard definition, the C language has made great sacrifices in ease of use for the sake of compatibility. In the book "C Traps and Defects", most of the error-prone points in the application process are sorted out. This article is a condensed version of "C Traps and Defects". If you want a more detailed explanation, you can check the original work.

2. Common mistakes

1. Keywords

1.1 = and ==

=: represents assignment in C language. Typical usage is: a = b, which means assigning the value of b to a.
==: As a logical judgment of equality in C language, the typical usage is: if (a == b), which means to judge whether a and b are equal.
    For example, there is a requirement to run a certain logic if a and b are equal, but due to the programmer's negligence, the following code was written:

if (a = b)
{
    
    
	/* do something */
}

    This should be familiar to you. One of the most classic stories is that a certain space agency wrote a "==" instead of a "=" due to the carelessness of the programmer, causing the rocket launch to fail. In the book C Expert Programming, the author puts the blame on the C language standard. Because of the standard definition of C, all C compilers will not check this exception, but regard it as a normal operation for programmers.

1.2 & and | are different from && and ||

    & and | are bitwise AND and bitwise OR, && and || are logical AND and logical OR. Those who are accustomed to using C language may not have a high probability of making mistakes on this point, but if it is cross-language development, it is easy to confuse these two symbols.

1.3 Single characters and double characters

    Some of the symbols in the C language are single-character symbols, and some are double-character symbols, so there will inevitably be some ambiguity. For example, the following code originally means that y is equal to x divided by the value pointed to by the pointer p.

y = x/*p;

    But because / takes precedence and is combined with *, it becomes a comment symbol of /*. However, this kind of error is difficult to occur in a modern editing environment, because the editor will recognize the part after /* as a comment when you write this kind of statement.
    There is another possibility that the compiler cannot recognize, like the following.

a=-1;

    In the old version of the C language, it is allowed to use =+ to represent +=, and =- to represent -=, so the original intention of the above is to assign a value of -1 to a, and the result of the compiler is that a is decremented by 1.

    Because of the above pitfalls, for C language, it is best for us to have a simple set of coding rules to avoid the above problems. Here are a few points related to this.

    Add spaces on both sides of the equal sign and spaces on both sides of the arithmetic sign.

y = a + b;	/* 推荐 */
y=a+b;		/* 不推荐 */

    *When used as a reference character, use it close to variables

y = *p;		/* 推荐 */
y = * p;	/* 不推荐 */

    When adding comments, because // and /* are used, there is / in them. In order to prevent / from being abnormally combined with other characters, it is also recommended to add a space after // or /* to distinguish them.

// 推荐
//不推荐

/* 推荐 */
/*不推荐*/

1.4 Integer variables

    If the first character of an integer variable is 0, the constant will be treated as an octal number. This way of writing is prohibited by the ANSI C standard, but sometimes for the sake of code alignment and beauty, it may be written like this:

/* 错误写法 */
uint32_t Table[] =
{
    
    
	012, 032, 054, 022,
	123, 456, 321, 051
};

    Writing this way will cause 012 to become 10 (decimal), 032 to 26 (decimal), 054 to 44 (decimal), 022 to 18 (decimal), and 051 to 41 (decimal).

/* 正确写法 */
uint32_t Table[] =
{
    
    
	 12,  32,  54,  22,
	123, 456, 321,  51
};

1.5 Single quotes and double quotes

    A character enclosed in single quotes represents an integer, while a character enclosed in double quotes represents a pointer .
    How to understand it? Let’s first look at the following two ways of writing.

char *p = "Hello world!\n";
printf(p);

char p[] = {
    
    'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', 0};
printf(p);

    The two running results are completely consistent, but from the perspective of physical storage, the two are different.
    First, after compilation, the string constant "Hello world\n\0" will first be stored in the constant area, and then when the pointer p is defined, the p pointer initially points to the beginning of the string.
    Second, after the same compilation, the characters "Hello world\n\0" will be stored in the constant area (but it should be stored without duplication, and the actual compiled size is smaller than the entire string above) , and then when defining the p array, the characters will be copied to the array one by one as the initial value.
    So from a space point of view, the first method saves a part of the ram space occupied than the second method.

    Having said the above, now let’s talk about the error-prone point. What problems will occur if single quotes and double quotes are mixed? Let's take a look at the following example.

printf("\n");	/* 正常的写法 */
printf('\n');	/* 错误的使用 */

    At present, we know that the first parameter passed to printf is of pointer type, so its operating mechanism is that when "\n" is passed in, the actual function internally indexes the position of the string \n through the address for printing. But the second way of writing becomes that the incoming address is actually the value of the character \n, that is, the incoming pointer address is 10 (the ASCII code value of \n), so printf will go to the 10 address to find the string. End printing until \0 is encountered.

2. Grammar

2.1 Function declaration

    Starting from the most basic definition, how to declare what data type a variable or constant is?

int main(void)
{
    
    
	unsigned char apple = 0;

	/* 声明20这个值是unsigned char,这种方式我们也称之为数据类型强转 */
	apple = (unsigned char)20;
}

    Next, increase the difficulty. If we are now required to jump to address 0 and run, how should we implement it in C language? This can be extended to how C language implements jump. There is a very common jump method in C language, which is function. For example, if a function A is defined now, and the address of this function A happens to be 0, then calling this function A is equivalent to jumping to the 0 address. On the other hand, if I treat address 0 as a function operation, can I achieve a program jump? According to the previous analysis, let’s implement it simply. Let’s first look at how ordinary functions are called.

/* 定义一个函数 */
void Func(void)
{
    
    

}

int main(void)
{
    
    
	/* 函数的调用就是函数名加上()来实现 */
	Func();
}

    Now that we have the basic syntax of the function above, we need to treat the 0 address as a function. Can we operate it as follows?

int main(void)
{
    
    
	/* 把0当成函数来操作 */
	0();
}

    This is obviously not possible. We can look at the basic composition of the function. In addition to function parameters, the function also has a return value. Here, the number 0 is directly referenced by adding (). The compiler does not know this "function" What is the parameter type and the return data type, so if the compiler cannot understand something, it will naturally report an error. So how should we tell the compiler what kind of function type this 0 is? This is where our magical pointers are used.

unsigned char *p;

    Let's see that this pointer also has a data type. This data type represents the data type of the address pointed to by this pointer. Have you found a useful thing? When defining a pointer, you can define a data type for the address pointed by the pointer. Putting aside the pointer, you can define a data type for the address. So now as long as we define a pointer pointing to 0, and define the address pointed by this pointer as a function type, wouldn't the above problem be perfectly solved? Here we take a look at how function pointers should be defined and referenced.

/* 定义一个函数指针 */
void (*pData)(void);
int main(void)
{
    
    
	pData = 0;

	/* 注意这里只是一种简写,写全应该是"*pData();",不过ANSI C允许下面这种简写形式 */
	pData();
}

    So back to the original question, if you don’t want to introduce pointer variables here and just want to directly declare that address 0 is a function pointer, what should you do?

int main(void)
{
    
    
	/* 把中间(void (*)(void))这部分抽出来,就是普通的指针引用 */
	(*(void (*)(void))0)();
}

    If you think the above operation is too simple, you can take a look at the following operation.

int main(void)
{
    
    
	/* 我不是针对你,我是说在座的各位,都是xx */
	(*(void(*(*)(void (*)(void)))(void))0)((void (*)(void))1)();
}

    The above operation can actually be simplified through typedef. The simplified effect is as follows.

typedef void (*FUNCA)(void);
typedef FUNCA (*FUNCB)(FUNCA);

int main(void)
{
    
    
	/* 简化后的操作,学废了么 */
	(*(FUNCB)0)((FUNCA)1)();
}

    The flexibility of pointer operations is beyond your imagination. This is where function declarations are prone to errors.

2.2 Operator precedence

Insert image description here

    Because each operator has precedence, when operators are mixed, the actual effect may be different from what was imagined. For example, *p++ is actually considered *(p++) by the compiler. For another example, if you want to determine whether two flags exist and a certain bit is both 1, you can write it as follows:

if (flags & FLAG != 0)

    But because != has a higher priority than &&, the above statement will be interpreted by the compiler as:

if (flags & (FLAG != 0))

    This leads to the fact that except when FLAG is exactly 1 or 0, this formula is wrong when FLAG is other numbers.

    But when you actually write code by yourself, there is no need to memorize this table. You only need to add brackets to the statements that need to be executed first. After all, brackets are free and you can add them as you like, as long as they do not affect reading.

2.3 Semicolon ending mark

    Normally, writing an extra semicolon in a C program may not have any impact: the extra semicolon may be regarded as an empty statement that will have no actual effect. But if these extra semicolons follow if or while, it may affect the original logic. as follows:

if (x[i] > big);
	big = x[i];

    Or if there is a semicolon missing, the program logic will be very different:

if (n < 3)
	return
logrec.data = x[0];
logrec.time = x[1];
logrec.code = x[2];

    There are also cases where if does not end with curly braces, such as the following. The original intention is that if condition a is met, functions b, c, and d will be executed.

if (a)
	b();
	c();
	d();

    But the actual effect is as follows. If condition a is met, function b will be executed, and then functions c and d will be executed.

if (a)
{
    
    
	b();
}
c();
d();

    So this also triggered some programming standards.
    When using if/for/while, add curly braces no matter how many statements are executed by the execution condition.

/* 推荐 */
if (a)
{
    
    
	b();
}

/* 不推荐 */
if (a) b();

2.4 switch-case

    Generally speaking, in addition to case, switch also has default and break. A complete switch-case syntax is as follows:

switch (a)
{
    
    
	case 0:
	{
    
    
		dosomethingA();
		break;
	}
	case 1:
	{
    
    
		dosomethingB();
		break;
	}
	default:
	{
    
    
		ErrorFunc();
		break;
	}
}

    If break is missing here, then if the value of input a is 1, according to the original logic, it should exit after executing dosomethingB. However, without break, the program will not exit the switch statement after executing dosomethingB and will continue to execute ErrorFunc. However, in some scenarios, break is specifically omitted. For example, the C language implementation of Duff's device can improve the efficiency of loop execution. If you are interested, you can check out its principle. The source code is below

/* to为拷贝的目标缓存,from为拷贝的源数据缓存,count为拷贝数据个数 */
void  fDuffDevice(  int  *  to,  int  *  from,  int  count)
{
    
    
	int n = (count + 7 ) / 8 ;
	switch (count % 8 ) 
	{
    
    
		case 0 :    do {
    
      * to ++ = * from ++ ;
		case 7 :          * to ++ = * from ++ ;
		case 6 :          * to ++ = * from ++ ;
		case 5 :          * to ++ = * from ++ ;
		case 4 :          * to ++ = * from ++ ;
		case 3 :          * to ++ = * from ++ ;
		case 2 :          * to ++ = * from ++ ;
		case 1 :          * to ++ = * from ++ ;
						} while ( -- n >    0 );
	}  
}

2.5 A murder caused by an else

    First understand a definition. Else is always combined with the nearest unmatched if within the same bracket . For example, here is a piece of code:

if (x == 0)
	if (y == 0)	error();
else
{
    
    
	z = x + y;
	f(&z);
}

    Judging from the indentation, the author originally wanted to perform the operation of judging y when x is 0, and perform addition when it is not 0. However, C language is not recognized by indentation like Python, so the actual logic of the code is as follows:

if (x == 0)
{
    
    
	if (y == 0)
	{
    
    
		error();
	}
	else
	{
    
    
		z = x + y;
		f(&z);
	}
}

    So again, try to add curly braces, and it is recommended to put an else after each if, even if the code is not executed in the else.

/* 推荐 */
if (a)
{
    
    
	dosomething();
}
else
{
    
    
	/* 不需要执行 */
}

/* 不推荐 */
if (a)
	dosomething();

3. Semantics

3.1 Pointers and arrays

    These two are the two brothers who have the most connections. People often compare them. Some say they are the same, while others say they are different. Let’s get straight to the conclusion. The two are definitely different. If they are the same, why would we design such two syntaxes? I won’t talk about other common points here, but focus on the biggest difference between them.

    Array : The compiler has already determined the starting address of the array when compiling. If the array name is treated as a pointer, it is a constant pointer (that is, the address pointed to is immutable). So this so-called "pointer" itself does not take up space.
    Pointer : The pointer address is variable, so the pointer itself needs to occupy 32 bits of space (depending on the system, if it is a 64-bit system, it occupies 64 bits of space) to store the address corresponding to the pointer.

    Apart from the above properties, the two are basically the same in other aspects. But there is a point that some newbies can easily get confused. For example, for one-dimensional arrays, the operations of arrays and pointers are interchangeable.

unsigned char Arr[10];
unsigned char *P = Arr;

/* 下面这两个操作是等效的 */
P[3] = 2;
*(Arr + 3) = 2;

    But when it comes to two-dimensional arrays, many people make the following mistakes.

unsigned char Arr[2][3];
unsigned char **P = Arr;

/* 下面这个操作编译器会报错 */
P[1][2] = 20;

    The correct way of writing should be as follows.

unsigned char Arr[2][3];

/* 正确写法一 */
unsigned char (*P)[3] = Arr;
P[1][2] = 20;

/* 正确写法二 */
unsigned char *P = &Arr[0][0];
*(P + 3 * 1 + 2) = 20;

3.2 Copy of pointers

    In Java, there are deep copy and shallow copy. Deep copy means completely copying a copy of the data, while shallow copy means copying the pointer of the data. Switching to C language, a mistake that many novices easily make before they fully understand pointers is copying pointer data, such as the following situation.

char *p, *q;
p = "abc";
q = p;

    Here p is a pointer to the string 'a''b''c''\0'. When p is assigned to q, it actually just makes q point to this string instead of copying a copy of the string. , so when the operation p[1] = 'd', the value of q[1] will also become 'd'.

3.3 A null pointer is not an empty string

    A null pointer means that the value of the pointer is 0, that is, the pointer points to the 0 address. The empty string means that there are no elements in the string. Strictly speaking, the first character is '\0', so the two operations cannot be equivalent.

3.4 Boundary calculation

    This can be said to be a novice killer, and even veterans sometimes stumble here, because when operating arrays in C language, the subscript starts from 0, so when there is an array with n elements, its operable subscript The standard range is 0~n-1. So take a look at the following types of codes to see if you have encountered any problems in them.

unsigned char i = 0;
unsigned char a[10];
/* 实际循环赋值了11次 */
for (i = 0; i <= 10; i++)
{
    
    
	a[i] = 0;
}
/* 实际dosomething执行了times+1次 */
void Function(unsigned char times)
{
    
    
	do
	{
    
    
		dosomething();
	}
	while (times--);
}

    If you are really not sure, it is recommended to substitute a small value into the brain test.

3.5 Evaluation order

    There are two ways to write autoincrement and decrement, one is as a variable prefix, and the other is as a variable suffix.

/* 先返回n的结果,再对n进行自加减操作 */
n++;
n--;

/* 先对n进行自加减操作,再返回自加减后n的结果 */
++n;
--n;

    When this increment/decrement variable is used in multiple places in the same statement, there is more emphasis on the execution order.

i = 0;
while (i < n)
	y[i] = x[i++];

    What's the problem? The above code assumes that y[i] is assigned before the increment operation of i, but ANSI C does not guarantee this for you. In other words, there is no clear definition in ANSI C for the processing order here, so the processing results of different compilers may be different. For this kind of problem, the safest way is to execute the increment/decrement in a single statement.

3.6 Shortest execution path

    This is generally reflected in judgment statements, such as the following code:

unsigned char a(void);
unsigned char b(void);
int main()
{
    
    
	if (a() && b())
		return 0;
}

    If it is required that both a and b must be executed, then in the above code, whether b is executed depends entirely on a's mood. When a function returns a false result, no matter whether b returns a true or false result, this judgment is not satisfied, so the program It will directly skip the running of b and end this judgment.

/* 规范性写法 */
unsigned char a();
unsigned char b();

int main()
{
    
    
	unsigned char c, d;
	/* 确保a和b都有执行 */
	c = a();
	d = b();
	if (c && d)
		return 0;
}

3.7 Implicit conversion of data types

    Just like the following simple example.

if (a - b < 0)
{
    
    
	printf("%d - %d < 0\n", a, b);
}
else
{
    
    
	printf("%d - %d >= 0\n", a, b);
}

    The execution result of this code depends entirely on the data types of a and b and the values ​​of a and b themselves. For example, in the following situations, the execution results will be completely different.

/* 这种定义结果是10 - 20 >= 0 */
unsigned int a = 10;
unsigned int b = 20;

/* 这种定义结果是10 - 20 < 0 */
signed int a = 10;
signed int b = 20;

/* 这种定义结果是10 - 20 < 0 */
unsigned char a = 10;
unsigned char b = 20;

    In order to avoid this situation, the safest way is to add a temporary variable cache, convert "implicit" to "explicit", clarify the data type of the calculation result, and then perform the next step of comparison and calculation.

unsigned char a = 10;
signed short b = 20;
signed int c = a - b;
if (c < 0)
{
    
    
	printf("%d - %d < 0\n", a, b);
}
else
{
    
    
	printf("%d - %d >= 0\n", a, b);
}

4. Link

    Code compilation is generally divided into several stages, precompilation -> compilation -> assembly -> linking. For novices, many people are not clear about this process, which will inevitably lead to some errors.

4.1 Definition and declaration

    In the same source file, if the definition is inconsistent with the declaration, an error will be reported during compilation. However, if the definition and declaration are not in the same source file, the compiler's check will be bypassed, resulting in some strange problems. Take the example below.

/********************** A.c ***********************/
float i = 20.22;
/**************************************************/

/********************** B.c ***********************/
extern unsigned char i;
/* 这里使用i时,其数据已丢失了大部分 */
printf("%d\n", i);
/**************************************************/

    In addition, if the definition and declaration of functions are inconsistent, the results will be unsatisfactory.

/********************** A.c ***********************/
unsigned char Func(unsigned char i)
{
    
    
	printf("函数内打印\n");
	return i;
}
/**************************************************/

/********************** B.c ***********************/
unsigned char Func(void);
int main()
{
    
    
	printf("返回值:%d\n", Func());
	return 0;
} 
/**************************************************/

4.2 Naming conflict

    When two variables or functions with the same name are defined in the same source file, a naming conflict will occur and a compilation error will be reported. However, when defined in different source files, an error will not necessarily be reported. Even ANSI C allows you to do this. For example, the ANSI C standard library provides some functions. If a function with the same name is defined externally, ANSI C will "hide the name" and call the external function first. However, if a third party in the same project wants to use the definition in the original ANSI C standard library, the refactored function may be called unintentionally. The best way to solve this problem is to use the static modifier in the usage area to limit the scope of redefinition.

4.3 Precompilation and linking

    First, you need to understand the entire compilation process of the code. First, the compiler recognizes the preprocessing instructions and will precompile it first. Then it compiles a single file into an independent .o file, and then links the independent .o file. Link into .a file.
    Because the execution sequence is like this, there are many exceptions like this. For example, in the following code, guess what the result is printed out?

/* 枚举定义 */
enum emModuleType
{
    
    
	MODULE_TYPE_normal = 0,
	MODULE_TYPE_plus = 1,
};

/* 宏定义 */
#define MODULE_TYPE		1

int main(void)
{
    
    
	/* 条件编译 */
	#if (MODULE_TYPE == MODULE_TYPE_plus)
		printf("MODULE_TYPE = MODULE_TYPE_plus\n");
	#else
		printf("MODULE_TYPE = MODULE_TYPE_normal\n");
	#endif
}

    The answer is announced, and the result is printed:

MODULE_TYPE = MODULE_TYPE_normal

    Why? Looking back at the compilation sequence mentioned earlier, this code first performs the preprocessing starting with #, so when preprocessing is performed, the two enumerations in the enum have not yet been assigned a value, that is, both are 0. , so the above code is equivalent to the following code.

#define MODULE_TYPE		1
int main(void)
{
    
    
	#if (MODULE_TYPE == 0)
		printf("MODULE_TYPE = MODULE_TYPE_plus\n");
	#else
		printf("MODULE_TYPE = MODULE_TYPE_normal\n");
	#endif
}

5. Preprocessing

5.1 Spaces in macro definitions

    Spaces in macro definitions cannot be added arbitrarily, because the syntax of macro definition itself uses spaces to identify the subject of replacement, so in the following example, its meaning will be completely different.

/* 定义一个f(x),用于替换((x) - 1) */
#define f(x) ((x) - 1)

/* 定义一个f,用于替换(x) ((x) - 1) */
#define f (x) ((x) - 1)

5.2 Macros are not functions

    A common example is how to write a macro that calculates the product of two numbers.

#define MUL(x, y) (x * y)

    If it is operated like a function, there is no problem with the following calculation. However, this is a macro. The macro directly expands the passed in variables, and the calculation result is very different from the original idea.

/* 宏展开得到(1 + 2 * 3 + 4),结果为11而不是预想的21 */
MUL(1 + 2, 3 + 4);

5.3 Macros are not statements

    The general grammatical habit is to add a semicolon at the end of the statement. If the macro definition is also used as a statement, sometimes there will be such embarrassing situations. for example

/* 自定义一个断言宏,如果传入e条件不满足,则终止程序 */
#define assert(e)  if (!(e)) assert_error(__FILE__, __LINE__)

    Because it is considered that a semicolon will be added at the end of the statement during application, no semicolon is added here in the macro definition. If this is used in actual applications, there will be some subtle errors.

if (x > 0 && y > 0)
	assert(x > y);
else
	assert(x > y);

    If assert is a function, then the above operation is no problem. Unfortunately, it is a macro. Expanding it becomes the following

if (x > 0 && y > 0)
	if (!(x > y)) assert_error(__FILE__, __LINE__);
	else
		if (!(x > y)) assert_error(__FILE__, __LINE__);

    To solve this problem, add a curly brace after the macro definition

#define assert(e)  {
      
      if (!(e)) assert_error(__FILE__, __LINE__);}

    But this will cause a new problem. After expanding in the above way, it will become like this again, because there is a semicolon before else, which becomes a grammatical error.

if (x > 0 && y > 0)
	{
    
    if (!(x > y)) assert_error(__FILE__, __LINE__);};
else
	{
    
    if (!(x > y)) assert_error(__FILE__, __LINE__);};

    Therefore, it is difficult and long to operate macros completely as statements. However, there is a writing method that is relatively common in macro definitions that can solve the above problems, which is to add do while.

#define assert(e)  \
	do\
	{
      
      \
		if (!(e)) assert_error(__FILE__, __LINE__);\
	}while(0)

5.4 Macros are not type definitions

    Some definitions look the same as typedef, which leads some people to think that they can be equivalently replaced, but this is not the case.

typedef unsigned char* pU8

/* 使用pU8定义两个指针变量A和B */
pU8 A, B;
#define pU8 unsigned char*

/* 同样用pU8来定义两个变量,这时候直接按宏展开,会得到unsigned char *A和unsigned char B */
pU8 A, B;

    Therefore, it is recommended that all type redefinitions use typedef.

6. Portability defects

6.1 Changes to C language standards

    For example, the following writing method is supported in C99, but is not supported by the old standard.

int main()
{
    
    
	for (int i = 0; i < 20; i++);
	return 0;
}

/* 旧标准的写法 */
int main()
{
    
    
	int i;
	for (i = 0; i < 20; i++);
	return 0;
}

6.2 System digits

    For 32-bit systems, int represents a 32-bit integer, but in an 8-bit system, int represents a 16-bit integer. This is the difference in data types caused by the difference in system digits.
    To solve this problem, you can use the definition in the C standard library <stdint.h>. The advantage is that when porting to a system with different bit numbers, you only need to modify this header file without modifying a lot of code. The following is taken from the stdint.h file.

/* Exact integral types. */
 
/* Signed. */
 
/* There is some amount of overlap with <sys/types.h> as known by inet code */
#ifndef __int8_t_defined
# define __int8_t_defined
typedef signed char     int8_t;
typedef short int       int16_t;
typedef int         int32_t;
# if __WORDSIZE == 64
typedef long int        int64_t;
# else
__extension__
typedef long long int       int64_t;
# endif
#endif
 
/* Unsigned. */
typedef unsigned char       uint8_t;
typedef unsigned short int uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int        uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int   uint64_t;
#else
__extension__
typedef unsigned long long int uint64_t;
#endif

6.3 Big and small endian

    Big endian means that internal data access is high-order first and low-order end, while little endian is the opposite. As far as microcontrollers are concerned, the STM32 is a little-endian system, and the 51 microcontroller is a big-endian system. So what impact will this difference have? The conversion of high and low bits of data is mostly used in the two fields of communication and storage. For example, now a 32-bit data 0x12345678 is stored in the off-chip Flash. In a big-endian system, when it is stored off-chip, the data is 0x12345678. If this off-chip Flash is given to the little-endian system to obtain the data, it will also be 32-bit. To obtain the data, the data will become 0x78563412. The same problem exists in the field of communications.

/* Flash读写接口 */
void FlashWrite(unsigned char *data, unsigned int num);
void FlashRead(unsigned char *data, unsigned int num);

/* 大端系统写入,存储的数据为0x12345678 */
unsigned int DataWrite = 0x12345678;
FlashWrite(&DataWrite, 4);

/* 小端系统读出,DataRead为0x78563412 */
unsigned int DataRead;
FlashRead(&DataRead, 4);

    One way to eliminate this problem is to operate all data by bytes, and stipulate that the high bit is in front and the low bit is in the back, or the low bit is in front and the high bit is in the back.

/* Flash读写接口 */
void FlashWrite(unsigned char *data, unsigned int num);
void FlashRead(unsigned char *data, unsigned int num);

/* 32位数据转成4字节数据的数组 */
void Int32to8_HtoL(unsigned int data, unsigned char *buff)
{
    
    
	buff[0] = (unsigned char)data >> (0 * 8);
	buff[1] = (unsigned char)data >> (1 * 8);
	buff[2] = (unsigned char)data >> (2 * 8);
	buff[3] = (unsigned char)data >> (3 * 8);
}

/* 4字节数据的数组转成32位数据 */
void Int8to32_HtoL(unsigned int *data, unsigned char *buff)
{
    
    
	*data = ((unsigned int)buff[0] << (0 * 8))
	      | ((unsigned int)buff[1] << (1 * 8))
	      | ((unsigned int)buff[2] << (2 * 8))
	      | ((unsigned int)buff[3] << (3 * 8));
}

/* 大端系统写入,存储的数据为0x12345678 */
unsigned int DataWrite = 0x12345678;
unsigned char Buff[4];
Int32to8_HtoL(DataWrite, &Buff[0]);
FlashWrite(&Buff[0], 4);


/* 小端系统读出,DataRead为0x78563412 */
unsigned int DataRead;
unsigned char Buff[4];
FlashRead(&Buff[0], 4);
Int8to32_HtoL(&DataRead, &Buff[0])

6.4 Sign bit of char

    For some compilers, the definitions of char types without unsigned and signed keywords are different. Some compilers default to char as a character, so it is unsigned, while others default to signed. Therefore, it is recommended that regardless of whether the data type is signed or unsigned, bring the unsigned/signed keyword, or use the standard library definition uint8_t/int8_t.

3. References

"C Traps and Pitfalls" [US] Andrew Koenig / People's Posts and Telecommunications Publishing House
"C Expert Programming" [US] Peter Van Der Linden / People's Posts and Telecommunications Publishing House

4. Related links

"C Language Application - Pointers"

Guess you like

Origin blog.csdn.net/u012749085/article/details/130729924