鱼C论坛

 找回密码
 立即注册
查看: 3494|回复: 15

[已解决]关于指针类型的问题

[复制链接]
发表于 2019-8-8 13:47:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 micolar 于 2019-8-8 13:52 编辑
  1. #include<stdio.h>
  2. int main(){
  3.         char a[3][3] = {
  4.                 {48,49,50},
  5.                 {51,52,53},
  6.                 {54,55,56}
  7.         };
  8.         printf("%c",*(char*)a);
  9.         return 0;
  10. }
复制代码


上面我定义了一个二维数组
因为数组名a 本身应该可以看成一个指针
一开始下意识输出格式是直接*a
然后我意识到指针也有类型
所以又改成了*(char*)a

但是又有个两个问题
1.int 假如是4个字节 char 1个字节 如果把一个指向int类型的指针强制换位char类型的指针
它读取是32位的前八位还是后八位

2.各个数组的行列可能是不同的
如果又有一个数组 char b[4][5]
那么(*b)++怎么理解
换句话说对于指针,它除了保存地址,还保存什么信息,它又以什么方式保存
c语言的指针地址是实际的机器地址 还是虚地址
最佳答案
2019-8-8 15:17:00
问题1:
这个问题和运行环境有关

例如有一个32位数据 0x12345678
如果这样的代码运行在小端机器上,读取到的是 0x78
如果这样的代码运行在大端机器上,读取到的是 0x12 (这是一个理论上的答案,毕竟我没有大端的机器,没有测试过,不过我认为这个理论上的答案是正确的,你就认为是正确的就好了,^_^)
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2019-8-8 15:17:00 | 显示全部楼层    本楼为最佳答案   
问题1:
这个问题和运行环境有关

例如有一个32位数据 0x12345678
如果这样的代码运行在小端机器上,读取到的是 0x78
如果这样的代码运行在大端机器上,读取到的是 0x12 (这是一个理论上的答案,毕竟我没有大端的机器,没有测试过,不过我认为这个理论上的答案是正确的,你就认为是正确的就好了,^_^)
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 15:27:56 | 显示全部楼层
问题2:
  1. char b[4][5]
复制代码


数组名表示数组第0个元素的地址
直接写 b 和写 &b[0] 是一样的

(*b)++ 也就是 (*&b[0])++
这里先取地址然后立刻解引用,& 和 * 相互抵消,也就是
(*b)++
(*&b[0])++
(b[0])++
b[0]++
上面这些是一样的
(*b)++ 和 b[0]++ 是一样的

未完待续。。。
^_^
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 15:44:47 | 显示全部楼层
继续问题2:

  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         char b[4][5] = {{0}};
  5.         (*b)++;
  6.         return 0;
  7. }
复制代码


这个代码是错的,编译会提示
  1. E:\tmp>gcc -g -Wall -o main main.c
  2. main.c: In function 'main':
  3. main.c:6:6: error: lvalue required as increment operand
  4.   (*b)++;
复制代码


1.png

因为 (*b) 不是 lvalue,所以不能 ++

未完待续。。。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 15:56:28 | 显示全部楼层
本帖最后由 人造人 于 2019-8-8 15:59 编辑

既然 (*b) 不是 lvalue,不能 ++
那什么是 lvalue ?

就我个人的理解
能放在等于号左边的一切,都是 lvalue

例如
int a = 100;
a = 50;
a出现在了等于号左边,a是 lvalue,因此 a++是正确的

char b[10];
b[0] = '0';
b[0]出现在了等于号左边,b[0]是 lvalue,因此 b[0]++是正确的

与此相对
int c = 10;
100 = c;
100是一个常数,不能出现在等于号左边,因此100不是lvalue,100++不正确的

还有
const int d = 100;
d = 10;
d是const的,d不能出现在等于号左边,d不是lvalue,因此 d++不正确的
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         const int d = 100;
  5.         d = 10;
  6.         d++;
  7.         return 0;
  8. }
复制代码
  1. E:\tmp>gcc -g -Wall -o main main.c
  2. main.c: In function 'main':
  3. main.c:6:4: error: assignment of read-only variable 'd'
  4.   d = 10;
  5.     ^
  6. main.c:7:3: error: increment of read-only variable 'd'
  7.   d++;
  8.    ^~
复制代码


这个代码编译提示错误

有lvalue就有rlvalue
是lvalue就一定是rlvalue,因为能出现在等于号左边的东西,一定能出现在等于号右边
是rlvalue就不一定是lvalue,因为100能出现在等于号右边,作为一个常数,但是100不能出现在等于号左边,100是常数,不能赋值
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:04:44 | 显示全部楼层
回到问题2:
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         char b[4][5] = {{0}};
  5.         (*b)++;
  6.         return 0;
  7. }
复制代码


这个代码提示错误,说 (*b) 不是 lvalue

(*b)++
(*&b[0])++
(b[0])++
b[0]++
上面这些相同

(*b) 不是 lvalue
b[0]也不是 lvalue

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:10:22 | 显示全部楼层
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         char b[4][5] = {{0}};
  5.         b[0] = 10;
  6.         return 0;
  7. }
复制代码

  1. E:\tmp>gcc -g -Wall -o main main.c
  2. main.c: In function 'main':
  3. main.c:6:7: error: assignment to expression with array type
  4.   b[0] = 10;
  5.        ^
  6. main.c:5:7: warning: variable 'b' set but not used [-Wunused-but-set-variable]
  7.   char b[4][5] = {{0}};
  8.        ^
复制代码


b是一个二维数组,或者说数组的数组

如果你把 b[4] 看成一个整体 X,那么
char b[4][5] 就是 char X[5]

b是一个数组的数组,就是数组中存储的是数组

你直接写 b 相当于写 &b[0]
你直接写 b[0] 相当于写 &b[0][0]
你直接写 b[1] 相当于写 &b[1][0]
你直接写 b[2] 相当于写 &b[2][0]

找规律很重要,找到规律后你就可以以不变应万变,死记硬背是没用的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:16:21 | 显示全部楼层
b[0]相当于&b[0][0]

一旦对一个东西用了 & 符号,那么这个东西就成了rlvalue

例如
int a = 100;
int *pa = NULL;
pa = &a;
&a = 0;

a是int,&a是 rlvalue,&a = 0;是错误的


b[0]相当于&b[0][0]
一旦对一个东西用了 & 符号,那么这个对象就成了rlvalue
++需要一个lvalue,b[0]是一个rlvalue,b[0]++是错误的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:21:05 | 显示全部楼层
本帖最后由 人造人 于 2019-8-8 16:30 编辑

问题2的一半已经解释完了,现在看另一半

”换句话说对于指针,它除了保存地址,还保存什么信息,它又以什么方式保存”
对于指针,除了保存地址信息,还保存类型信息,对于以什么方式保存,这个我不知该如何回答

”c语言的指针地址是实际的机器地址 还是虚地址”
当然是虚拟地址

虚拟地址、逻辑地址、线性地址、物理地址的区别
https://blog.csdn.net/qiuchaoxi/article/details/79616220
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:37:10 | 显示全部楼层
  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         int a = 100;
  5.         char b = '\n';

  6.         printf("&a = %p\n", &a);
  7.         printf("&a + 1 = %p\n", &a + 1);
  8.         printf("\n");
  9.         printf("&b = %p\n", &b);
  10.         printf("&b + 1 = %p\n", &b + 1);
  11.         return 0;
  12. }
复制代码

  1. E:\tmp>gcc -g -Wall -o main main.c

  2. E:\tmp>main
  3. &a = 0xffffcbfc
  4. &a + 1 = 0xffffcc00

  5. &b = 0xffffcbfb
  6. &b + 1 = 0xffffcbfc
复制代码


对于指针,除了保存地址信息,还保存类型信息
可以看到对于int指针加1,地址值加了4
char指针加1,地址值加了1

如果一个指针只知道地址信息,不知道类型信息,就不能进行指针运算,因为没办法知道如何计算地址值


小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 16:40:52 | 显示全部楼层
完了
^_^
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-8-8 16:52:59 | 显示全部楼层
人造人 发表于 2019-8-8 16:21
问题2的一半已经解释完了,现在看另一半

”换句话说对于指针,它除了保存地址,还保存什么信息,它又以什 ...

我的两个小问题咋麻烦您写那么多

我犯糊涂了 (*b)++
我又忘了它是个指针常量

假设b是个数组呗  
b+1 就是它行的第二列的地址
这应该就是类型信息了 我是不是可以猜它用4个字节的内存来保存长度信息 毕竟数组长度可以取的很长 一个字节不够的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-8-8 16:56:54 | 显示全部楼层
人造人 发表于 2019-8-8 16:37
对于指针,除了保存地址信息,还保存类型信息
可以看到对于int指针加1,地址值加了4
char指针加1 ...

嗯嗯 我接触指针的时候就是这么想的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 17:41:19 | 显示全部楼层
micolar 发表于 2019-8-8 16:52
我的两个小问题咋麻烦您写那么多

我犯糊涂了 (*b)++

编译器并没有把指针的类型信息保存在编译后的程序中,编译器直接在编译程序时完成了这个类型信息相关的处理

  1. #include <stdio.h>

  2. int main(void)
  3. {
  4.         int a = 100;
  5.         int *pa = &a + 4;
  6.         return 0;
  7. }
复制代码

  1.         .file        "main.c"
  2.         .text
  3.         .def        __main;        .scl        2;        .type        32;        .endef
  4.         .globl        main
  5.         .def        main;        .scl        2;        .type        32;        .endef
  6.         .seh_proc        main
  7. main:
  8.         pushq        %rbp
  9.         .seh_pushreg        %rbp
  10.         movq        %rsp, %rbp
  11.         .seh_setframe        %rbp, 0
  12.         subq        $48, %rsp
  13.         .seh_stackalloc        48
  14.         .seh_endprologue
  15.         call        __main
  16.         movl        $100, -12(%rbp)
  17.         leaq        -12(%rbp), %rax
  18.         addq        $16, %rax
  19.         movq        %rax, -8(%rbp)
  20.         movl        $0, %eax
  21.         addq        $48, %rsp
  22.         popq        %rbp
  23.         ret
  24.         .seh_endproc
  25.         .ident        "GCC: (GNU) 7.4.0"
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-8-8 17:42:43 | 显示全部楼层
编译器直接把 &a + 4 翻译成了对变量a的地址值加16
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2019-8-8 18:13:10 | 显示全部楼层
人造人 发表于 2019-8-8 17:42
编译器直接把 &a + 4 翻译成了对变量a的地址值加16

嗯嗯我懂了谢谢
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-16 04:56

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表