1.数组下标编号为什么从0开始,而不是1开始?
- 假设从数组a[i]标号1开始计数,数组访问方式如下:
第1个元素的地址=首地址a;
第2个元素的地址=首地址a + 1元素大小;
第3个元素的地址=首地址a + 2元素大小;
…
第i个元素的地址=首地址a + (i-1)*元素大小;
在使用1作为编号时,在计算地址时总要进行一次减法运算;
- 假设从数组a[i]标号0开始计数,数组访问方式如下:
第0个元素的地址=首地址a;
第1个元素的地址=首地址a + 1元素大小;
第2个元素的地址=首地址a + 2元素大小;
…
第i个元素的地址=首地址a + i*元素大小;
在使用0作为编号时,在计算地址时不进行减法运算,这样用违背常规的方式换来计算速度。
2.结构体和共同体
2.1.定义:
- struct:把不同类型的数据组合成一个整体,自定义类型。
- union: 使几个不同类型的变量共同占用一段内存。
2.2.地址对齐
基本数据(如单字节、双字节、四字节整数)存放处的地址必须能被自己数据类型的大小整除。比如,双字节整数存放的地址必须被2整除,四字节整除存放的地址要被4整除。如果不满足要求,不同体系结构的cpu反应不同。默认对齐方式有1字节,2字节,4字节,8字节和16字节对齐。
struct和union都有内存对齐,结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项。
2.3.struct和union区别
- 在struct中,各成员都占有自己的内存空间,它们是同时存在的。sizeof(struct)是内存对齐后所有成员长度的加和。
- 在任何同一时刻,union只存放了一个被选中的成员,当union中存入新的数据后,原有的成员就失去了作用,新的数据被写到union的地址中。sizeof(union)是最长的数据成员的长度。
- 在union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度。对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的。
2.4.结构体对齐
案例1(visual studio2017):
#include<stdio.h>
struct Persion
{
char age;
short no;
};
int main()
{
struct Persion p;
p.age = 10;
p.no = 2;
printf("albert:%d\n",sizeof(p));
return 0;
}
VS选择对齐方式为1字节:
通过watch窗口查看各个变量:
变量p.age地址为0x0037f794,占一个字节;变量p.no地址为0x0037f795,占两个字节;所以按照常理:sizeof p 应该是3个字节。
根据对齐原则,双字节整数存放的地址必须被2整除,而p.no地址为奇数,不能被2整除,造成该变量存放的位置没有对齐。
VS选择对齐方式为2字节:
通过watch窗口查看各个变量:
变量p.age地址为0x0027fa68,占2个字节;变量p.no地址为0x0027fa6a,占两个字节;sizeof p = 4个字节。同理如果选择对齐方式为4,8,16字节,变量p.age占2个字节;变量p.no占两个字节;sizeof p = 4个字节。
案例2:
将short no;改为int no;
#include<stdio.h>
struct Persion
{
char age;
int no;
};
int main()
{
struct Persion p;
p.age = 10;
p.no = 2;
printf("albert:%d\n",sizeof(p));
return 0;
}
VS选择对齐方式为2字节:
通过watch窗口查看各个变量:
变量p.age地址为0x0039f948,占2个字节;变量p.no地址为0x0039f94a,占4个字节;sizeof p = 6个字节。
VS选择对齐方式为4字节:
通过watch窗口查看各个变量:
变量p.age地址为0x0014fc08,占4个字节;变量p.no地址为0x0014fc0c,占4个字节;sizeof p = 8个字节。
小结:
- 变量p.age长度是根据p.no变量类型和对齐方式有关,长度如下表所示:
- 变量p.no所占长度 = min{max{sizeof(成员变量)},对齐长度}。
案例3:
如下所示,sizeof p = 16。
#include<stdio.h>
struct Persion
{
char a;
char b;
int c;
short d;
char e;
short f;
};
int main()
{
struct Persion p;
p.a = 1;
p.b = 2;
p.c = 3;
p.d = 4;
p.e = 5;
p.f = 6;
printf("albert:%d\n", sizeof(p));
return 0;
}
优化后:sizeof p = 12
#include<stdio.h>
struct Persion
{
char a;
char b;
char e;
short d;
short f;
int c;
};
int main()
{
struct Persion p;
p.a = 1;
p.b = 2;
p.c = 3;
p.d = 4;
p.e = 5;
p.f = 6;
printf("albert:%d\n", sizeof(p));
return 0;
}
2.5.Union
2.5.1.大小端
- 大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
- 小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
union 型数据所占的空间等于其最大的成员所占的空间。对union 型的成员的存取都是相对于该联合体基地址的偏移量为0 处开始,也就是联合体的访问不论对哪个变量的存取都是从union 的首地址位置开始。
int main(int argc, char *argv[])
{
union
{
int i;
char a[2];
}*p, u;
p = &u;
p->a[0] = 0x39;
p->a[1] = 0x38;
printf("albert:%d\n",sizeof(u)); //4
printf("albert:0x%x\n",u.i); //0xcccc3839
}
如下所示,&u.i和&u.a[0]都是相同的地址,所以对数组赋值保存到 i。
2.5.2.union 内存对齐
#include <stdio.h>
int main(int argc, char *argv[])
{
union u1
{
double a;
int b;
}p1;
union u2
{
char a[13];
int b;
}p2;
union u3
{
char a[13];
char b;
}p3;
printf("albert:%d\n", sizeof(p1)); //8
printf("albert:%d\n", sizeof(p2)); //16
printf("albert:%d\n", sizeof(p3)); //13
}
对于u2和u3,最大的空间都是char[13]类型的数组,为什么u3的大小是13,而u2是16呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。对界是可以更改的,使用#pragma pack(x)宏可以改变编译器的对界方式,
3.i++和++i 汇编理解
3.1. i++
#include <stdio.h>
int main()
{
int a;
int i = 10;
a = i++;
printf("a=%d,i=%d\n",a,i); //输出a=10,i=11
}
汇编执行如下:
3.2. ++i
#include <stdio.h>
int main()
{
int a;
int i = 10;
a = ++i;
printf("a=%d,i=%d\n",a,i); //输出a=11,i=11
}
汇编如下:
4.指针汇编新解
案例1: p++ 先将p赋值给变量b,然后执行p++,即p执行数组下一个元素。
#include <stdio.h>
int main()
{
int a[] = { 1,5,10,20 };
int *p = &a[0];
int b = *p++; //*p++ 等同于*(p++)
printf("b = %d\n",b); //b= 1
printf("*p = %d\n", *p); //*p=5
}
汇编如下所示:
案例2:(p)++ 先将p赋值给变量b,然后将*p+1。
#include <stdio.h>
int main()
{
int a[] = { 1,5,10,20 };
int *p = &a[0];
int b = (*p)++;
printf("b = %d\n",b); // b =1
printf("*p = %d\n", *p); //*p=2
}
案例3: *++p 先将p变量+4,指向下一个数据元素,然后取此时p指向的数据。
#include <stdio.h>
int main()
{
int a[] = { 1,5,10,20 };
int *p = &a[0];
int b = *++p; //等同于*(++p)
printf("b = %d\n",b); //b = 5
printf("*p = %d\n", *p); //*p = 5
}
案例4: ++*p 先取p指向的值,然后将该值自增1。
#include <stdio.h>
int main()
{
int a[] = { 1,5,10,20 };
int *p = &a[0];
int b = ++*p; //等同于++(*p)
printf("b = %d\n",b); //b = 2
printf("*p = %d\n", *p); //*p = 2
}
案例5: *d++ = *s–;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char *src = "hello,world";
char *dest = NULL;
int len = strlen(src);
dest = (char *)malloc(len + 1);
printf("len=%d\n", len);
char *d = dest;
char *s = &src[len - 1];
while (len-- != 0)
*d++ = *s--;
*d = 0;
printf("*dest=%s\n", dest);
free(dest);
dest = NULL;
return 0;
}