目录
一、strlen和strlen
sizeof
类型:运算符(编译时求值)
功能:计算变量或类型所占的内存字节数
参数:可以是类型名或变量名
计算时机:编译时确定
包含内容:计算整个数组/结构体的大小(包括填充字节)
对指针:返回指针本身的大小(通常4或8字节)
对字符串:包括末尾的'\0'空字符
strlen
类型:库函数(运行时计算)
功能:计算字符串的长度(不包括结尾的'\0')
参数:必须是字符串(以'\0'结尾的字符数组)
计算时机:运行时遍历字符串直到遇到'\0'
包含内容:只计算'\0'前的字符数
对指针:如果指向有效字符串则返回字符串长度
对字符串:不包括末尾的'\0'
区别
计算时机:sizeof编译时,strlen运行时
包含内容:sizeof包含所有内存,strlen只计字符
参数类型:sizeof接受类型/变量,strlen只接受字符串
效率:sizeof无运行时开销,strlen需遍历字符串
详见博客:C语言:strlen与sizeof详解_4 *sizeof(int)中的*什么意思-CSDN博客
二、数组和指针题目
注意:1、sizeof(数组名),数组名表示整个数组。计算的是整个数组的大小,单位是字节
2、&数组名,数组名表示整个数组。取出的是整个数组的地址
除此之外,所有的数组名都是首元素地址。
一维数组
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
打印结果为(64位):
1. sizeof(a)
a是数组名,单独出现在sizeof中,代表整个数组,输出16。
2. sizeof(a + 0)
a是数组首元素的指针(即int*类型),所以a+0是指针(指向数组的第一个元素),输出8。
3. sizeof(*a)
a是数组名,单独出现时代表数组首元素的地址,但是这里,*a表示取首元素的值,类型是int,所以sizeof(*a)就是sizeof(int),输出4。
4. sizeof(a + 1)
a为指针(指向首元素),a+1指向数组的第二个元素(即a[1]的地址),所以a+1是一个指针,输出8。
5. sizeof(a[1])
a[1]是数组的第二个元素,类型为int,输出4。
6. sizeof(&a)
&a表示取整个数组的地址,它是一个数组指针(类型为int(*)[4]),输出8。
7.sizeof(*&a)
&a是整个数组的地址,类型为int(*)[4]。然后对它解引用(*&a),得到整个数组,输出16。
8. sizeof(&a + 1)
&a是数组的地址,类型是int(*)[4]。&a+1表示跳过了整个数组,指向数组之后的位置(但仍然是一个指针),输出8。
9. sizeof(&a[0])
&a[0]是取数组第一个元素的地址,即一个int*类型的指针,输出8。
10. sizeof(&a[0] + 1)
&a[0]是第一个元素的地址,加1后是第二个元素的地址(即&a[1]),是int*指针,输出8。
字符数组
1.1
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
return 0;
}
打印结果为(64位):
1. sizeof(arr)
arr代表整个数组,数组有6个char类型元素,
输出6。
2. sizeof(arr+0)
arr作为数组名,这里会退化为指向首元素的指针(即char*类型),arr+0仍然是一个指向首元素的指针,
输出8。
3. sizeof(*arr)
arr退化为首元素的指针,*arr就是首元素,类型是char,
输出1。
4. sizeof(arr[1])
arr[1]是数组的第二个元素,即'b',类型为char,
输出1。
5. sizeof(&arr)
&arr是取整个数组的地址,类型是char (*)[6](指向长度为6的字符数组的指针),
输出8。
6. sizeof(&arr+1)
&arr是指向数组的指针,加1则跳过整个数组,指向数组之后的位置,类型还是char (*)[6],输出8。
7. sizeof(&arr[0]+1)
&arr[0]是第一个元素的地址,类型是char*,加1指向第二个元素(即arr[1]),所以还是一个char*指针,
输出8。
1.2
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
该代码无法通过编译运行
1.strlen(arr)
找不到'\0',
输出随机值。
2.strlen(arr + 0)找不到'\0',
输出随机值。
3.strlen(*arr)*arr是arr[0],值为'a'(类型char,ASCII值97)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将97解释为内存地址,访问无效地址(如0x61),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
4.strlen(arr[1])arr[1]是'b'(类型char,ASCII值98)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将98解释为内存地址,访问无效地址(如0x62),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
5.strlen(&arr)找不到'\0',
输出随机值。
6.strlen(&arr + 1)找不到'\0',
输出随机值。
7.strlen(&arr[0] + 1)找不到'\0',
输出随机值。
同时注意:strlen接收的参数类型为const char *,所以这里会直接报错:
2.1
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
}
打印结果为(64位):
1.sizeof(arr)
arr是数组名,在sizeof中单独使用,表示整个数组的大小。数组有7个字符(包括'\0'),
输出7
2.sizeof(arr + 0)
这里,arr + 0:数组名arr在表达式中(不是单独在sizeof中)会退化为指向首元素的指针,所以arr+0是一个指针(指向首元素地址),
输出8
3.sizeof(*arr)
arr退化为指针,然后解引用得到第一个元素,即字符a,
输出1
4.sizeof(arr[1]))
arr[1]是数组的第二个元素,即字符'b',
输出1
5.sizeof(&arr)
&arr:取整个数组的地址,这是一个指向数组的指针(类型为char(*)[7]),
输出8
6.sizeof(&arr + 1)
&arr是整个数组的地址,加1会跳过整个数组,指向数组末尾之后的位置。但类型仍然是char(*)[7],所以它还是一个指针,
输出8
7.sizeof(&arr[0] + 1)
&arr[0]是第一个元素的地址(char*类型),然后加1得到第二个元素的地址(即指向'b'的指针),
输出8
2.2
int main()
{
char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
}
该代码无法通过编译运行
1.strlen(arr)
arr是数组首元素地址,统计\0之前出现的字符个数,输出6。
2.strlen(arr + 0)arr+0是数组首元素地址,统计\0之前出现的字符个数,输出6。
3.strlen(*arr)*arr是arr[0],值为'a'(类型char,ASCII值97)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将97解释为内存地址,访问无效地址(如0x61),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
4.strlen(arr[1])arr[1]是'b'(类型char,ASCII值98)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将98解释为内存地址,访问无效地址(如0x62),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
5.strlen(&arr)&arr 是整个数组的地址。虽然它的值(即起始地址)与`arr`相同,但类型是`char(*)[7]`(指向长度为7的字符数组的指针)。但是,当传递给strlen时,它会被转换为const char*,因此仍然指向数组的起始位置。
因此,从数组起始位置开始,直到遇到空字符,长度为6
输出:6
6.strlen(&arr + 1)&arr表示数组地址,+1跳过了整个数组,找不到'\0',
输出随机值。
7.strlen(&arr[0] + 1)&arr[0] 是指向第一个元素的指针(即字符'a'的地址),然后加1,指向第二个元素(即字符'b'的地址)。
从字符'b'开始,字符串变为"bcdef",长度为5。
输出:5
3.1
int main()
{
const char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
}
打印结果为(64位):
1. sizeof(p):
p是一个指针,
输出8
2. sizeof(p + 1):
p + 1是指针运算,结果仍然是一个指针(指向字符串的第二个字符,即 'b' 的地址)。
输出8
3. sizeof(*p):
*p是解引用指针,得到的是字符a,类型是const char。
输出1
4. sizeof(p[0]):
p[0]等价于*(p+0),即*p,
输出1
5. sizeof(&p):
&p是取指针p的地址,因此得到的是一个指向指针的指针(即const char**类型)。
输出8
6. sizeof(&p + 1):
&p是指向p的指针,类型为const char**(实际上是指向const char*的指针,所以其类型为 `const char**`,&p+1就是指向下一个位置,但类型不变)。
输出8
7. sizeof(&p[0] + 1):
p[0]是字符 'a',所以&p[0]是p本身(即指向 'a' 的指针,类型为const char*)。
&p[0] + 1相当于p + 1,即指向 'b' 的指针,类型仍为const char*。
输出8
3.2
int main()
{
const char* p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));
return 0;
}
该代码无法通过编译运行
1. strlen(p)
p是一个指向字符串字面量abcdef的指针。字符串abcdef有6个字符,以\0结尾,
输出6。
2. strlen(p + 1)
p + 1指向p所指向地址的下一个字符。p指向a,所以p + 1指向b。
输出5。
3. strlen(*p)
*p是解引用指针p,所以它是字符'a'。strlen期望一个指针,但这里传递的是一个字符(char类型)。在C语言中,字符会被提升为整数,但这不是一个有效的指针。这会导致未定义行为,因为strlen会尝试将 `'a'` 的ASCII值(97)解释为地址。
输出:编译错误或运行时崩溃(无法输出整数)。
4. strlen(p[0])
同上,
输出:编译错误或运行时崩溃(无法输出整数)。
5. strlen(&p)
&p是取指针p本身的地址。p是一个指向字符的指针,所以&p是一个指向指针的指针(char**类型),&p指向存储指针p的内存地址,它不是一个以`'\0'`结尾的字符串,找不到'\0',
输出随机值
6. strlen(&p + 1)
&p + 1:&p是p的地址,类型是char**。由于p是一个指针变量,&p + 1指向p之后的内存地址(偏移一个指针大小),它不是一个以`'\0'`结尾的字符串,找不到'\0'。
输出随机值
7. strlen(&p[0] + 1)
p[0]是'a',所以&p[0]是取p[0]的地址,即p本身,因为p[0]等同于*(p + 0),所以&p[0]就是p。
因此,&p[0] + 1等同于p + 1,和第二个调用相同。
输出5
二维数据
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
}
打印结果为(64位):
1. sizeof(a)
a 是整个二维数组,有3行4列,所以总元素个数为3*4=12,每个元素是int(4字节),所以总大小为12*4=48字节。
输出:48
2. sizeof(a[0][0])
a[0][0] 是数组的第一个元素,类型是int,所以大小为4字节。
输出:4
3. sizeof(a[0])
a[0] 是二维数组的第一行,它是一个一维数组,包含4个int。所以大小是4*4=16字节。
注意:a[0]的类型是int[4]。
输出:16
4. sizeof(a[0] + 1)
a[0] 是第一行的数组名,在这里不会单独作为数组大小使用,而是发生了“退化”(decay)成为指向该行首元素的指针(即int*),所以a[0]+1是指向第一行第二个元素的指针(地址)。在64位系统中,指针的大小是8字节。
注意:这里不是sizeof(整个数组),所以数组名退化为指针。
输出:8
5. sizeof(*(a[0] + 1))
a[0]+1是指向第一行第二个元素的指针(即&a[0][1]),解引用后得到a[0][1],类型是int,所以大小是4字节。
输出:4
6. sizeof(a + 1)
a是二维数组名,在表达式中使用(除了sizeof和&)会退化为指向第一行的指针(即int(*)[4])。所以a+1是指向第二行的指针(即&a[1])。它仍然是指针,所以大小为8字节。
输出:8
7. sizeof(*(a + 1))
a+1是指向第二行的指针,解引用后得到第二行的整个数组(即a[1]),类型是int[4],所以大小为4*4=16字节。
输出:16
8. sizeof(&a[0] + 1)
&a[0] 是取第一行的地址(类型为int(*)[4]),加1后指向第二行的地址(即&a[1]),所以这是一个指针,大小为8字节。
输出:8
9. sizeof(*(&a[0] + 1))
*(&a[0] + 1) 等价于a[1],即第二行(一个包含4个int的数组),所以大小为16字节。
输出:16
10. sizeof(*a)
a 是二维数组名,在表达式中退化为指向第一行的指针(即int(*)[4]),解引用后得到第一行(即a[0]),类型为int[4],大小为16字节。
注意:*a 等价于 a[0]。
输出:16
11. sizeof(a[3])
a[3] 看起来是访问第四行(越界),但sizeof是在编译时求值,它不会实际访问这个元素,而是根据类型来确定大小。a[3]的类型是int[4](因为a是3行4列,但类型是确定的),所以大小是16字节。
注意:sizeof不会计算表达式的值,只关心类型。虽然a[3]越界,但类型是明确的。
输出:16
注:
这里s没有被赋值,就是因为sizeof在编译时求值时,不会实际访问这个元素,而运算求值是在链接之后。
三、指针运算题目
1
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。
&a+1:跳过整个数组,指向a[5](并不存在,但地址是合法的),随后ptr-1指向a[4],即5。
2
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
假设p的值为0x100000,计算结构体大小为32字节(64位),
1. printf("%p\n", p + 0x1);
指针p加上1,实际上是加上1个结构体的大小,即32字节(0x20)。所以结果应该是 p + 0x20(注意,这里0x1代表1个单位,单位是结构体的大小)。但是,输出是%p,所以会以指针形式输出。输出0x100020(因为32的十六进制是0x20)。
2. printf("%p\n", (unsigned long)p + 0x1);
unsigned long是一个整数类型,所以这里就是简单的整数加法。p的值转换后的整数也是0x100000,加1就是0x100001。
3. printf("%p\n", (unsigned int*)p + 0x1);
这里将p转换为unsigned int*指针,然后加1。unsigned int的大小是4字节。所以加1就是增加4个字节。假设p是0x100000,那么转换后指向0x100000,加1后指向0x100004。
3
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;
}
结果为4,未知值(200000)。(%x表示以十六进制形式输出整数)
1. int* ptr1 = (int*)(&a + 1);
&a 是整个数组的地址,其类型为 int(*)[4]。因此,&a + 1 将会跳过整个数组(即4个int,16字节),所以ptr1指向数组a最后一个元素(4)的下一个位置,即数组a之后的位置。然后将这个地址强制转换为int*类型,赋值给ptr1。那么,ptr1[-1] 相当于 *(ptr1-1)。由于ptr1指向数组a之后的位置,所以ptr1-1指向数组a的最后一个元素,即4。
2. int* ptr2 = (int*)((int)a + 1);
a是数组首元素的地址,类型是int*。但是这里将a直接转换为int,所以a+1就是数值上的+1。然后,再将(int)a + 1 强制类型转化为(int*),此时ptr指向原地址偏移1字节后的位置,*ptr读取从该偏移位置开始的4 字节的数据。由于内存中整数可能按小端序存储(低位字节在前),这会导致读取到跨越两个元素的部分字节,结果通常是一个未知的值(200000)。
4
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
结果为1。
代码中使用了圆括号(0,1),为逗号表达式,实际赋值的是逗号右侧的值。
(0, 1):返回1。
(2, 3):返回3。
(4, 5):返回5。
实际上该数组被初始化为{1,3,5,0,0,0},故a[0]是数组为{1,3}的地址,p[0]就是1。
5
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;
}
指针p是数组指针,指向一个包含4个整数的数组,此时p+1跳过4个整型16个字节。a表示首元素地址,类型为int(*)[5],强制赋值后,p就指向了该二维数组的首元素地址。
-4在内存中为10000000 00000000 00000000 00000100(补码)
原码为11111111 11111111 11111111 11111100,即为0xff ff ff ff ff ff ff fc
打印结果为(64位):
6
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
}
打印结果为at。
创建了一个包含 3 个元素的指针数组a
,
每个元素都是指向字符串常量的指针,其内存布局为:a[0] → "work\0"
a[1] → "at\0"
a[2] → "alibaba\0"
而pa是一个指向指针的指针,初始指向a[0]的地址,pa++后指向a[1]的地址,所以结果为at
7
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *-- * ++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
return 0;
}
打印结果为:
本题的关键在于把图画出来:
1.**++cpp
++cpp:cpp指向cp[1]
*cpp:获取cp[1],即c+2
**cpp:获取*(c+2),即"POINT"
输出:POINT2.*-- * ++cpp + 3
++cpp:cpp此时 指向cp[2]
*cpp:获取cp[2],即c+1
--*cpp:cp[2]修改为c
*--*cpp:获取*c,即"ENTER"
*--*cpp + 3:从"ENTER"偏移 3,得到"ER"
输出:ER
3.*cpp[-2] + 3cpp[-2]:cp[0],即c+3
*cpp[-2]:*(c+3),即"FIRST"
*cpp[-2] + 3:从"FIRST"偏移 3,得到"ST"
输出:ST
4.cpp[-1][-1] + 1cpp[-1]:cp[1],即c+2
cpp[-1][-1]:*(c+1),即"NEW"
cpp[-1][-1] + 1:从"NEW"偏移 1,得到"EW"
输出:EW
以上就是一些关于指针进阶的题目,感谢阅读。