1、memcpy函数、memset函数
memcpy函数它是一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)函数原型为:
void *memcpy(void *destin, void *source, unsigned n);
功能:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
memset函数是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。
void *memset(void *s, int c, size_t n);
注:函数格式为memset(首地址,值,sizeof(地址总大小));
其中:
s指向要填充的内存块。
c是要被设置的值。
n是要被设置该值的字符数。
返回类型是一个指向存储区s的指针。
2、uint8_t,uint16_t是啥?
*_t是typedef定义的表示标志,是结构的一种标注。即我们所看到的 uint8_t、uint16_t、uint32_t都不是新的数据类型,而是通过typedef给类型起得别名。其中uint8_t是用1个字节表示的;uint16_t是用2个字节表示的;uint32_t是用4个字节表示的。
(一个字节 = 8位(8个二进制位) 1Byte = 8bit;
一个十六进制 = 4个二进制位
一个字节 = 2个十六进制)
下面是官方给的 typedef:
/* stdint.h standard header */
#pragma once
#ifndef _STDINT
#define _STDINT
#ifndef RC_INVOKED
#include <crtdefs.h>
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
/*Unsigned*/
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
3、sizeof(结构体)
内存对齐
结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
结构体每个成员相对于结构体首地址的偏移量都是当前成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
说明:
1、基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型;
2、对于复合数据类型,如结构体嵌套结构体,那么基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型;
3、我认为计算结构体大小的时候,主要用到准则2和准则3,对于准则1是编译器自动完成的,不需要过多理会。
4、C++中类的可以看做是特殊的结构体,所以类的sizeof的计算和结构体是一样的。
4、结构体与数组的数据转换(数组指针转结构体指针)
在进行通信的时候,传送的往往都是一些数组类型,而我们为了方便对数据的管理,往往都是使用结构体来存储数据。那么就涉及到如何把数组转换成结构体。数组指针强制转化为了结构体指针,在这一转化过后,数组中的值就会和结构体的值一一对应,并且和结构体中的位置有关。
4.1实现依据:
数据都是存放在内存中,这就需要考虑几个问题:
- 内存对齐
- 内存连续
4.2内存对齐
一、基本概念
字节对齐:计算机存储系统中以Byte为单位存储数据,不同数据类型所占的空间不同,如:整型(int)数据占4个字节,字符型(char)数据占一个字节,短整型(short)数据占两个字节,等等。
计算机为了快速的读写数据,默认情况下将数据存放在某个地址的起始位置,如:整型数据(int)默认存储在地址能被4整除的起始位置,字符型数据(char)可以存放在任何地址位置(被1整除),短整型(short)数据存储在地址能被2整除的起始位置。这就是默认字节对齐方式。
二、准则
其实字节对齐的细节和具体编译器实现相关,但一般而言,满足三个准则:
1、 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
2、结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
3、结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节
三、结构体长度求法
1.成员都相同时(或含数组且数组数据类型同结构体其他成员数据类型:
(1)结构体长度=成员数据类型长度×成员个数(各成员长度之和);
(2)构体中数组长度=数组数据类型长度×数组元素个数;
2.成员不同且不含其它结构体时
(1)分析各个成员长度;
(2).找出最大长度的成员长度M(结构体的长度一定是该成员的整数倍);
(3)并按最大成员长度出现的位置将结构体分为若干部分;
(4)各个部分长度一次相加,求出大于该和的最小M的整数倍即为该部分长度
(5)将各个部分长度相加之和即为结构
3.含有其他结构体时:
(1)分析各个成员长度; 对是结构体的成员,其长度按b来分析,且不会随着位置的变化而变化;
(2)分析各个成员的长度(成员为结构体的分析其成员长度),求出最大值;
(3)若长度最大成员在为结构体的成员中,则按结构体成员为分界点分界;
其他成员中有最大长度的成员,则该成员为分界点;
求出各段长度,求出大于该和的最小M的整数倍即为该部分长度
(4)将各个部分长度相加之和即为结构体长度:
注:1、空结构体的大小为1,编译器为其分配一个字节的空间站位;
2、有static成员的结构体,静态变量存放在全局数据区中,sizeof计算的是栈中分配空间的大小。
4.3内存连续
结构体所占用的内存是连续的,但其中各个成员不一定是连续存放的,要看你结构体中定义成员顺序是否有字节对齐.
#include <stdio.h>
#include <string.h>
//#define LEN 14
#define uint16_t unsigned short int
#define uint8_t unsigned char
typedef struct{
uint16_t Head;
uint8_t SourceID;
uint8_t DestinationID;
uint8_t Command;
uint8_t Data[7];
uint16_t Tail;
}FrameInfo_TypeDef;
constexpr int LEN=14;
unsigned char gFrameInfo[LEN];
unsigned char buffer[LEN];
int main(int argc, char *argv[])
{
FrameInfo_TypeDef* sFrameInfo = (FrameInfo_TypeDef*)gFrameInfo;
uint8_t i = 0;
for(;i<LEN;i++)
{
buffer[i] = i;
}
memset(gFrameInfo, 0, LEN);
memcpy(gFrameInfo, buffer, LEN);
for(i=0; i<LEN; i++)
{
printf("gFrame[%d] = %d \r\n.", i, gFrameInfo[i]);
}
printf("sFrameInfo.Head = %d\r\n", sFrameInfo->Head);
printf("sFrameInfo.SourceID = %d\r\n", sFrameInfo->SourceID);
printf("sFrameInfo.DestinationID = %d\r\n", sFrameInfo->DestinationID);
printf("sFrameInfo.Command = %d\r\n", sFrameInfo->Command);
for(i=0; i<7; i++)
{
printf("sFrameInfo.Data[%d] = %d \r\n.", i, sFrameInfo->Data[i]);
}
printf("sFrameInfo.Tail = %d\r\n", sFrameInfo->Tail);
printf("Hello C-Free!\n");
return 0;
}
(主机字节序是小端存储,数据的高位放到高地址中,只要是Intel或AMD的x86/x64架构就一定是小端主机字节序。
5、什么是字节序?大端序?小端序?
字节序,又称端序或尾序(英语中用单词:Endianness 表示),在计算机领域中,指电脑内存中或在数字通信链路中,占用多个字节的数据的字节排列顺序,在几乎所有的平台上,多字节对象都被存储为连续的字节序列。
**在编写网络程序时,任何格式化的数据在传输时都应考虑字节序。**大端小端是对于高于一个字节的数据类型来说的,比如说int,short等。char 类型的话就不存在大小端的问题。
字节的排列方式有两个通用规则:
大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
小端序(Little-Endian),将一个多位数的低位放在较小的地址处,位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。
为何要有字节序
很多人会问,为什么会有字节序,统一用大端序不行吗?答案是,计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。在计算机内部,小端序被广泛应用于现代 CPU 内部存储数据;而在其他场景,比如网络传输和文件存储则使用大端序。
总结:
除了计算机的内部处理,其他的场合比如网络传输和文件储存,几乎都是用的大端字节序。正是因为这些原因才有了字节序。计算机处理字节序的时候,如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节。小端字节序则正好相反。
#include <stdio.h>
#include <string.h>
#include <iostream>
typedef unsigned char uchar;
typedef unsigned int uint;
//转换函数
//Temp 字符型数组
//pOut int型数组
//length 字符型数组的长度
//这里假设字符型数组里包含的都是偶数个元素
//如果是奇数个数,则需要增加如下处理:
//检查length是否是奇数,如果是,动态分配length+1个char大小的数组
//数据全部清零,并且把Temp的内容拷贝到动态分配的数组中
//按照下面长度是偶数的情况处理
//释放动态分配的内存
//这里根据问题要求,应该是以小端序来存放int类型的
//如果更智能一些,可以写一个检查字节序的函数来处理
void Exchange(const uchar *Temp, uint *pOut, uchar length)
{
int i = 0;
for (; i < length / 2; i++)
{
//小端序,直接按原来的顺序拷贝过去
//如果是大端序,则可以改变一下字节序
memcpy(pOut, Temp, 2);
pOut++;//int型指针后移一位
Temp += 2;//字符型字节后移两位
}
}
//测试代码
int main(void)
{
int i;
uint array[20] = { 0 };
uchar Start[] = { 0x80,0xF1,0x58,0x03,0xC1,0xEA,0x8F,0x06 };
Exchange(Start, array, sizeof(Start));
for (i = 0; i < 20; i++)
{
if (array[i] != 0)
{
printf("0x%04X ", array[i]);
}
}
printf("\n");
printf("%d\n", sizeof(Start));
system("pause");
return 0;
}
6、校验和计算
由于数据传输距离的因素影响,计算机和受控设备间的通信数据就常常出现不可预知的错误。为了防止这些错误所带来的影响,一般在通信时采取数据校验方法。为了保证数据在传输过程中不会出错,每个数据包后面一般都会加上校验字节。
说到检验和算法,比较熟悉的就是循环冗余算法(CRC),通常由CRC-8,CRC-16,以及CRC-32等,但是在资源相对比较紧张的一些平台上,运行CRC也比较吃力,或者对于需要进行快速校验的场合,所以这里可以使用简单的Checksum算法。
具体思路
这里以8位的Checksum为例,假设输入一组数据0XFF,0X05,0XC1,0X19;
(1)用16位变量保存数据的累加和;
(2)得到累积和之后,将累加和的高8位和低8位相加;
(3)将累加和进行取反操作;
checksum8算法实现:
uint8_t m_checksum_08(const uint8_t *pdata, uint32_t count)
{
register uint16_t sum = 0;
uint8_t* addr = (uint8_t *)pdata;
if(count <= 0){
goto error;
}
// Main summing loop
while(count >= 1)
{
sum = sum + *(uint8_t *) addr;
count --;
(uint8_t *) addr++;
}
// Add left-over byte, if any
if (count > 0)
sum = sum + *((uint8_t *) addr);
// Fold 16-bit sum to 8 bits
while (sum>>8){
sum = (sum & 0xFF) + (sum >> 8);
}
return (uint8_t)(~sum);
error:
return 0;
}
7、& 0xff的作用
&表示按位与,只有两个位同时为1,才能得到1, 0x代表16进制数,0xff表示的数二进制1111 1111 占一个字节.和其进行&操作的数,最低8位,不会发生变化。
作用1:只是为了取得低八位,通常配合移位操作符>>使用
例如:java socket通信中基于长度的成帧方法中,如果发送的信息长度小于65535字节,长度信息的字节
定义为两个字节长度。这时候将两个字节长的长度信息,以Big-Endian的方式写到内存中
out.write((message.length>>8)&0xff);//取高八位写入地址
out.write(message.length&0xff);//取低八位写入地址中
例如,有个数字 0x1234,如果只想将低8位写入到内存中 0x1234&0xff
0x1234 表示为二进制 0001001000110100
0xff 表示为二进制 11111111
两个数做与操作,显然将0xff补充到16位,就是高位补0
此时0xff 为 0000000011111111
与操作 1&0 =0 1&1 =1 这样 0x1234只能保留低八位的数 0000000000110100 也就是 0x34
8、QByteArray
(1)它提供一个字节数组,QByteArray可用于存储原始二进制字节(包括“\ 0” )和传统的8-bits的“\ 0” 端接字符串 . 使用QByteArray比使用const char *更方便。一般在需要传输原始数据和内存资源短缺时使用。
(2)QByteArray存储的是char型字符,继承自QMemArray< char >,但QByteArray提供的数组操作,比char更方便。
QByteArray byteData;
byteData.resize(sizeof(stSeaAir));
byteData.append(pHeader,(char*)&suavbitmap,sizeof(stuavbitmap));
9、记录
1、设备通信中qnetconfig IP和端口 dds中ip在修改本地ip后要对应起来修改