C语言--防坑指南

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,是一个指针占用空间

对于上面两个结构体有下面几点说明:

  1. array 结构体占用内存最小(由于数组没有元素,该数组在该结构体中不分配占用空间),array 1有个指针占用8B
  2. array 与前面结构体数据是连续的内存存储空间,而array 1下,新增加数据data是单独开辟的空间;
  3. 释放内存时,array 可以直接释放,而array 1需要先释放指针指向内存,然后再释放结构体数据部分否则会内存泄漏

总结如下:

  • 结构体最后使用0或1的长度数组的原因,主要是为了方便的管理内存缓冲区,如果你直接使用指针而不使用数组,那么,你在分配内存缓冲区时,就必须分配结构体一次,然后再分配结构体内的指针一次,(而此时分配的内存已经与结构体的内存不连续了,所以要分别管理即申请和释放)。

  • 而如果使用数组,那么只需要一次就可以全部分配出来,反过来,释放时也是一样,使用数组,一次释放,使用指针,得先释放结构体内的指针,再释放结构体。还不能颠倒次序。

  • 其实变长结构体就是分配一段连续的的内存,减少内存的碎片化,简化内存的管理。

变长结构体主要应用于:
<1>Socket通信数据包的传输;
<2>解析数据包;
<3>其他可以节省空间,连续存储的地方等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值