int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
2,5
上述代码第4行,&a取出的是数组的地址,+1后仍是数组地址,类型是数组指针类型,数组指针类型存进整型指针时需要强制类型转换。
//已知结构体Test类型的变量大小是20字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}* p;
//结构体指针创建p变量
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1); //0x1是16进制的1,和10进制的1值一样
printf("%p\n", (unsigned long)p + 0x1); //整型类型(整数)+1就是原来值+1
//指针类型先转10进制类型再+1
printf("%p\n", (unsigned int*)p + 0x1);
//unsigned int*无符号整型指针,+1跳过一个无符号整型(4字节)
return 0;
}
0000000000100020
0000000000100001
0000000000100004
::: tip 指针±整数运算,±的具体数字取决于指针类型。 :::
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
如上图,ptr1[-1]相当于 * (ptr1-1),ptr1是int*类型,大小为4字节,ptr1-1为4的地址,解引用得到4,即ptr1[-1]=4。 (int)a表示将数组a首元素地址强制类型转换成int,整数+1就是数字+1,然后再当作地址,相当于向后移动一个字节,ptr2的位置是图中红圈标识位置,小端存储模式下,还原为地址是0x02000000的地址,解引用后最前面的0省略,为0x2000000。 ::: warning 内存中最小内存单位是1字节,每个字节都有一个地址,即两个相邻地址相差一个字节。 :::
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
上述代码中a[3][2]的赋值是3个逗号表达式,取最后一个值,即a[3][2]={1,3,5}; 如上图,a[0]是第一行的数组名,没有单独放在sizeof和&后面,表示第一行数组的首元素地址即“1”的地址。p[0]==*(p+0)。p是“1”的地址,+0后不变,解引用得到“1”。
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
int(* p)[4];可得p是指向有4个整型元素的数组的指针。将a赋给p即a[0]数组的地址赋给p,赋值时会有警告,原因是p的类型是int(* )[4],a的地址是a[5]的地址,类型是int()[5]。可以忽略警告强行赋值。 *p[4] == * (p+4)** p[4][2] == *( *(p+4)+2)· ::: warning
int main()
{
int arr[] = { 1,2,3,4,5 };
int* p = arr;
*(p + 2) == arr[2];
*(p + 2) == *(arr + 2);
*(arr + 2) == arr[2];
return 0;
}
上述代码中第5行 * (p + 2)和arr[2]值相等,第4行可得出第6行,即 * (arr + 2) == arr[2]。 ::: p是int(*)[4]类型的指针,p+1就会跳过一个4个int类型元素的数组。 p+4指向的是数组a[3]中第二个元素的地址,p是指向数组的指针,解引用拿到的是上图中红框中四个元素的数组,相当于拿到了这四个元素的数组名,就是这四个元素的数组的首元素地址。 &p[4][2]对p[4][2]取地址,即上图 * (p+4)+2的位置。 指针相减得到的是指针之间的元素个数,如上图所示,&p[4][2]和&a[4][2]之间有4个元素,&p[4][2]在内存中的位置小于&a[4][2],结果为-4。 计算结果以%d的形式打印,输出-4。以%p的形式打印,结果是FFFFFFFC。 ::: tip -4的输出:
10000000000000000000000000000100 - -4原码
11111111111111111111111111111011 - -4反码
11111111111111111111111111111100 - -4补码
以%d的形式打印时,需要-4的原码,即-4。 以%p的形式打印时,认为内存中存放的是地址,地址不存在原反补和正负的区别,就会直接打印内存中存放的内容,即11111111111111111111111111111100 :::
int main()
{
int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d ,%d", *(ptr1 - 1), *(ptr2 - 1)); //10,5
return 0;
}
上述代码第5行 * (aa + 1)就是aa[1],表示第二行元素组成数组的地址即“6”的地址(也可以理解成二维数组aa表示首元素地址即第一行元素地址,+1跳过第一行,指向第二行的第一个元素即“6”的地址)。
int main()
{
char* a[] = { "work", "at", "alibaba"};
char** pa = a;
pa++;
printf("%s\n", *pa); //at
return 0;
}
::: warning
char* p = "abcdefg";
上述代码的写法是将常量字符串abcdefg首字符a的地址存放在指针p中。 ::: 数组a的内存布局:数组a中有3个元素,分别是w、a、a的地址,类型都是char * 。 数组名a是首元素地址即w的地址(类型char),存放在pa中(类型char)。 ::: tip char pa = a写法实际上是char * pa = a,* pa的* 表示pa是指针,char表示a是地址。 ::: pa++即pa+1跳过一个char 类型的变量,指向下一个char类型的变量即a的地址。 ::: tip *变量±整数 表示跳过 整数×类型 大小个字节。** ::: %s形式打印时以a的地址开始打印一个字符串,即打印“at”。
int main() {
char* c[] = { "ENTER","NEW","POINT","FIRST"};
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp); //POINT
printf("%s\n", *-- * ++cpp + 3); //ER
printf("%s\n", *cpp[-2] + 3); //ST
printf("%s\n", cpp[-1][-1] + 1); //EW
return 0;
}
指针数组c中存放4个字符指针,即4个字符串的首字符地址。 上述代码第6行,cpp先前置++,然后解引用得到“c+1”,前置--后变为c,再解引用得到“E”的地址,再+3指向字符串“ENTER”中的第二个字符“E”,%s从此处开始向后打印字符串,输出“ER”。 上述代码第7行,cpp[-2] == * (cpp-2),找到“c+3”,再解引用找到“F”的地址,+3向后偏移3个字节指向“S”,%s从此处开始向后打印字符串,输出“ST”。 上述代码第8行,cpp[-1][-1] == * (*(cpp-1)-1),第7行代码中cpp只参与运算不改变本身,cpp仍指向“c+1”,cpp-1指向“c+2”,解引用得到“c+2”,-1后变为“c+1”,解引用得到“N”的地址,+1向后偏移1个字节指向“E”,%s从此处开始向后打印字符串,输出“EW”。 ::: tip cpp自增和自减会改变cpp的值,cpp参与运算不改变cpp本身。 :::