1,sizeof和strlen
❀第一个例子:
char* ss = “0123456789”;
1、sizeof(ss)的结果是4,ss是指向字符串常量的字符指针
2、sizeof(*ss)的结果是1,ss是第一个字符
3、strlen(ss)的结果是10,它的内部实现是用一个循环计算字符串的长度,直到’/0’为止。 若有涉及strlen的字符串操作,记得要在最后加上‘\0’。
4、strlen(*ss)
错误,strlen的参数只能是char,且必须是以’/0’结尾的。
❀第二个例子:
char ss[] = “01233456789”;
1、sizeof(ss)结果是11,ss是数组,计算到’/0’的位置,因此是10+1
2、sizeof(*ss)结果是1,*ss是第一个字符
❀第三个例子
char ss[100] = “0123456789”;
1、sizeof(ss)的结果是100,ss表示在内存中预分配的大小:100*1
2、strlen(ss)的结果是10,它的内部实现是用一个循环计算字符串的长度,直到’/0’为止。 若有涉及strlen的字符串操作,记得要在最后加上‘\0’。
❀第四个例子
int ss[100] = “0123456789”;
1、sizeof(ss)的结果是400,ss表示在内存中的大小,为1004
2、strlen(ss)错误,strlen的参数只能是char,且必须是以’/0’结尾的。
❀第五个例子
#include<stdio.h>
#include<string.h>
void fun(int *P)
{
printf("在函数中:%d\n",sizeof(*P));
}
int main()
{
int A[10]={1,2,3};
printf("数组名:%d\n",sizeof(A));//40
fun(A);//4,在函数中退化成指针
return 0;
}
2,strncmp函数
用于比较两个字符串是否相等。
int strncmp ( const char * str1, const char * str2, size_t n );
防坑点:这里的长度 n 必须是 sizeof(str1) 或 strlen(str1)+1,保证“\0”也进行比较,否则n=2时“ab”和“abc”也被判为相等。
#include <stdio.h>
#include <string.h>
int main()
{
char arg[10];
scanf("%s", arg);
if(!strncmp("jack", arg, sizeof("jack"))){
printf("right !\n");
} else {
printf("%s is wrong\n", arg);
}
return 0;
}
3,strtol/strtoul/strtoull函数
strtol()会将参数nptr字符串根据参数base来转换成长整型数。
long int strtol(const char *nptr, char **endptr, int base);
strtol()会跳过参数nptr前面的空格字符,直到遇上数字或正负符号开始做base进制转换,再遇到非数字或字符串结束时(‘\0’)结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回;若参数endptr为NULL,则不返回非法字符串。
防坑点:如下使用 ‘\0’!=*endstr 确保arg中是全数字,但是arg若为NULL,转化后endstr 也为空,引用空指针便会出错。故无法确定arg是否为NULL时需要对endstr做非空判断处理
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *arg;
char *endstr;
scanf("%s", arg);
if(strtoul(arg, &endstr, 10)>65535 || !endstr || '\0'!=*endstr){
printf("wrong endstr: %s!\n", endstr);
} else {
printf("%d is OK!\n", strtoul(arg, &endstr, 10));
}
return 0;
}
4,建议calloc代替malloc
void *calloc(size_t n, size_t size);
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。
#include <stdio.h>
int main()
{
char *str=(char *)malloc(sizeof(char)*100); //相当于静态字符数组str[100],但大小可以更改的
typedef struct pointer{
int data;
struct pointer *p;
} pt;
pt *p=(pt *)calloc(1, sizeof(struct pointer)); //动态申请结构体空间
return 0;
}
5,对齐问题(__attribute__
属性)
我们在进行定义数据结构计算数据结构大小的时候,或者进行网络通讯的socket发送数据的时候,都会遇到一个共同的问题:数据对齐问题。这个问题是硬件为提高访问数据的效率引出的问题。程序编译器对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也可以屏蔽掉变量默认的对齐方式,自己设定变量的对齐方式。下面就简单的介绍常见的3种方法:
1,手动对齐,根据自己定义的数据结构,然后根据大小,按照一定的规律去排兵布阵,就可以让数据结构紧凑,尽量少的出现内存空隙。
这种方法实际上就是要求我们有一个好的编程习惯,这样可以提高我们代码的整洁性,也可以节省内存空间
2,使用#pragam pack(n) 对齐数据结构
这条预处理指令中的n就是按照那个字节进行对齐,n=1~4 并且必须是2的倍数
当n=4的时候,就是我们默认的数据结构对齐方式
当n=1的时候,也就是最节省空间的方法
以前受硬件条件的限制,我们能减少内存的占用就尽量少的占用内存,才会这样去做,不过现在的存储芯片,动辄上G,甚至上T,比以前的K级别的芯片容量大多了。
例如我们设置结构体的对齐方式:
struct student{
int age;
char c;
};
对于以上结构体,默认用sizeof输出大小为8字节,默认的字节对齐方式是4,当然我们也可以设置他的对齐 方式,如下:
#pragma pack(2)
struct student{
int age,
char c;
};
此时用sizeof输出大小是为6,因为对齐方式为2.如果设置对齐方式为1的话,sizeof输出大小则为5;
3,当然有时我们不希望编译器对结构体做对齐处理,而希望按照他原有的大小分配空间,这里就要用到 __attribute__((packed))
,这个意思是告诉编译器不要做对齐处理。
struct Student{
int age;
char c;
}__attribute__((packed));
int main(){
struct Student S;
printf("%d\n",sizeof(S));
}
输出大小为 5,其实和一字节对齐方式一样。使用 __attribute__ ((packed))
让编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,这样子就不会出现对齐的错位现象。
这种方式与上边的第二种方式是相同的
6,inline函数
代码量较小但使用频繁的函数作为inline函数,以此作为提高性能优化的途径之一。
比如
int g(int x) {
return x + x;
}
int f() {
return g();
}
这样f会调用g,然后g返回x + x给f,然后f继续把那个值返回给调用者。
如果g是inline的话。f会被直接编译成。
int f() {
return x + x;
}
相当于把g执行的操作直接融合到f里。这样减少了调用g消耗的时间,但同时也增大了f的尺寸。 这就是inline函数,也就是所谓的内联函数。
7,翻转问题
uint64_t get_msec(void)
{
uint64_t tsc = rte_get_tsc_cycles();
uint64_t hz = rte_get_timer_hz();
return (1000 * tsc / hz);
}
上面的代码用来获取从开机到现在的时间,最后乘以1000转化成毫秒数。
以3G主频举例,uint64_t 可以表示的持续时间最大值为:
0xFFFFFFFFFFFFFFFF = 18446744073709551615 = 6,148,914,691秒 = 195年。
但是,以上程序先乘以1000, 相当于函数返回值在0.195年也就是2个月左右时间就翻转了!
防坑点:运算必须先除后乘,防止溢出翻转,其他类似场景也要注意!
8,内存操作与管理
https://blog.csdn.net/qq_15437629/article/details/77415800#comments_18637898
9, 变长结构体
其实真正意义上并不是结构体的大小可变,而是使用结构体中的变量代表一个地址,从而访问超出结构体大小范围的数据。
如下所示,请注意看结构体中的最后一个元素,一个没有元素的数组。我们可以通过动态开辟一个比结构体大的空间,然后让buffer去指向那些额外的空间,这样就可以实现可变长的结构体了。更为巧妙的是,我们甚至可以用size存储data的长度。
struct array {
size_t size;
int data[0]; // strictly, it should be incomplete rather than zero sized
};
struct array * make_array(size_t size) {
struct array * array = malloc(sizeof (struct array) + size * sizeof (int));
array->size = size;
return array;
}
struct array * array = make_array(2);
array->data[1] = 42; // No problem: there's enough memory for two array elements
printf("%d\n", sizeof(struct array)); //=8, 变长数组不占内存,数组名只是一个符号
如果这里用char* data代替:
struct array1 {
size_t size;
int *data;
};
printf("%d\n", sizeof(struct array1)); //=16,是一个指针占用空间
对于上面两个结构体有下面几点说明:
- array 结构体占用内存最小(由于数组没有元素,该数组在该结构体中不分配占用空间),array 1有个指针占用8B
- array 与前面结构体数据是连续的内存存储空间,而array 1下,新增加数据data是单独开辟的空间;
- 释放内存时,array 可以直接释放,而array 1需要先释放指针指向内存,然后再释放结构体数据部分否则会内存泄漏
总结如下:
-
结构体最后使用0或1的长度数组的原因,主要是为了方便的管理内存缓冲区,如果你直接使用指针而不使用数组,那么,你在分配内存缓冲区时,就必须分配结构体一次,然后再分配结构体内的指针一次,(而此时分配的内存已经与结构体的内存不连续了,所以要分别管理即申请和释放)。
-
而如果使用数组,那么只需要一次就可以全部分配出来,反过来,释放时也是一样,使用数组,一次释放,使用指针,得先释放结构体内的指针,再释放结构体。还不能颠倒次序。
-
其实变长结构体就是分配一段连续的的内存,减少内存的碎片化,简化内存的管理。
变长结构体主要应用于:
<1>Socket通信数据包的传输;
<2>解析数据包;
<3>其他可以节省空间,连续存储的地方等。