V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Lxxyx
V2EX  ›  C

关于指针的疑惑, int **p[10], p 是数组还是指针?

  •  1
     
  •   Lxxyx · 2017-11-09 00:25:47 +08:00 · 5143 次点击
    这是一个创建于 2577 天前的主题,其中的信息可能已经有所发展或是发生改变。
    RT.
    遇到一个题目,问的是这个。

    我这儿的推断是,p 是一个指向指针数组的指针,所以是指针。

    因为 int *p[10]是指针数组,而第二个*号指向这个数组,所以是指针。

    但是答案告诉我是数组,所以迷惑不解,希望 V 友们可以给一个答案,谢谢啦~
    第 1 条附言  ·  2017-11-09 10:10:33 +08:00
    疑惑已解决,谢谢大家啦。
    68 条回复    2018-03-10 09:48:14 +08:00
    binux
        1
    binux  
       2017-11-09 00:30:53 +08:00
    那 int** p[10] 是数组还是指针?
    AttufliX
        2
    AttufliX  
       2017-11-09 00:31:36 +08:00 via Android   ❤️ 1
    指针,指向一个指向 10 个元素的数组的指针
    congeec
        3
    congeec  
       2017-11-09 00:33:43 +08:00   ❤️ 2
    https://cdecl.org/?q=int+**p%5B10%5D
    从右往左读,不过是 p 那块内存里存了 10 个 int **
    n2ex2
        4
    n2ex2  
       2017-11-09 00:35:15 +08:00 via Android   ❤️ 1
    **的数组...
    AttufliX
        5
    AttufliX  
       2017-11-09 00:35:38 +08:00 via Android
    上面说错了,cdel 查了下是数组,指向 10 个二维指针的数组
    geelaw
        6
    geelaw  
       2017-11-09 00:36:41 +08:00 via iPhone   ❤️ 1
    等同于 typedef int **ptr; ptr p[10];
    n2ex2
        7
    n2ex2  
       2017-11-09 00:39:46 +08:00 via Android
    不过数组名本身就是指针,要说它是指针也说得通...
    lianz
        8
    lianz  
       2017-11-09 00:42:40 +08:00   ❤️ 2
    数组就是指针,指针就是数组,两者本质上都是一个内存地址,可以直接换用,没任何区别。
    比如 :

    1:int girls[10] = {....}; 可以直接用指针访问访问:int i = *girls;
    2:int *p = (int*) malloc (sizeof(int) * 10); 可以直接用数组方式访问 int i = p[0];
    LeonLi
        9
    LeonLi  
       2017-11-09 00:45:00 +08:00
    其实 p 的类型是 int **[]
    XiaoxiaoPu
        10
    XiaoxiaoPu  
       2017-11-09 00:46:19 +08:00   ❤️ 2
    @lianz 数组当然不是指针,指针也不是数组,sizeof(int [10]) 跟 sizeof(int *) 能一样?

    @Lxxyx 《 C 专家编程》可以彻底解决你的疑问
    wsy2220
        11
    wsy2220  
       2017-11-09 00:47:28 +08:00   ❤️ 1
    数组, 数组的元素是指针
    LeonLi
        12
    LeonLi  
       2017-11-09 00:53:29 +08:00   ❤️ 2
    @lianz 你这个看起来类似是因为数组在访问的时候,其实是走的指针的语法糖的形式,但是说不上没有任何区别,最大的区别其实是数组是有自己的类型的,并不能跟指针等价。比如:
    1 数组名是不允许重复赋值的,指针可以
    int arr[1];
    int *p ;
    arr = xxx ;//error
    p = xxx://OK

    2 数组是和指针还有一个很明显的区别,数组可以使用 sizeof 算出整个数组大小,而指针的 sizeof 只是计算的指针分配空间的大小,以 32 位为例:
    int arr[10];
    int *p = (int*)malloc(sizeof(int)*10);
    size_t as = sizeof(arr);// 40
    size_t ps = sizeof(p);// 4
    hackpro
        13
    hackpro  
       2017-11-09 00:59:39 +08:00 via iPhone
    P 是一个是个元素的数组
    数组中每个元素是个指针
    指向另外一个 int *
    wwqgtxx
        14
    wwqgtxx  
       2017-11-09 01:23:43 +08:00   ❤️ 2
    @LeonLi
    @XiaoxiaoPu
    对于你们说的 sizeof 的问题插个话,这个地方完全是编译器实现的问题,在你用 int arr[10]这样定义的时候,sizeof(arr)编译器是完全能推断出来 arr 的 size 的,而如果你用动态分配的话,编译器只能知道这个指针本身多大,根本没办法知道这段内存地址的结尾在哪里,因为那个只有 malloc 的实现( glibc/msvcrt/...)才知道
    sizeof 运算符本质上就是一个预处理,在最后程序编译完了之后就消失了,被直接替换成具体的值了,所以 sizeof 所能得到的大小一定是可以静态推断的,这也就是对 std 容器进行求 sizeof 基本上毫无意义的原因
    FrankHB
        15
    FrankHB  
       2017-11-09 01:45:24 +08:00   ❤️ 1
    连完整的声明都不是,就半坨声明符,还想啥。
    p 就是个不知所云的标识符。没上下文姑且当作没声明了,没有对应的实体,无所谓指针数组。

    @wwqgtxx 如果只有某几句关键的废话,那么少妖言惑众,老实重修,算是你勉强表现出你楼上都没注意到的上下文相关特(废)色(物)特性的奖赏。但是非要满嘴跑火车……那么接好问题一箩筐:

    “完全是编译器实现的问题”?意思说换个编译器可以胡来了?
    推断……的 size ? Deduce 还是 infer ?
    内存?地址?结尾?哪个轮得到编译器管?哪个决定不能让编译器管?( C 艹 14 都钦定允许合并::operand new 了咋就没几个长眼的呢。)
    解释什么叫预处理。是不是对 phase of translation 毫无概念?
    “编译完了”?谁钦点要求编译的?
    直接“替换”?βη等价?
    “大小一定是可以静态推断的”?题主好像没说不能 VLA 吧?
    解释什么叫“基本上毫无意义”。不说语病……是不是不懂啥叫 incomplete type ?
    LeonLi
        16
    LeonLi  
       2017-11-09 01:56:41 +08:00
    @wwqgtxx
    首先说一下你说的编译器实现的问题。在 C 语言标准中,对于那些行为可以由编译器自行决定( implementation-defined behavior )和未定义行为( undefined behavior )是有严格区分的。
    以 C99 版标准为例,Rule6.5.3.4 规定了 sizeof 的行为( When applied to an operand that has array type, the result is the total number of bytes in the array.),这个是编译器不能自己改变其行为的。其中,编译器可以自行决定的只有 sizeof 的返回值类型 size_t。

    另外,回应一下你说的因为数组定义可以在编译器推断出来的问题。在 C99 标准之后,C 语言支持可变长数组 VLA ( variable-length array )。而 VLA 的 size 是在运行期才能确定的,此时,sizof 的结果由运行时(execution time)确定,返回的依然是整个数组的大小。所以你说的那段话是不成立的。
    LeonLi
        17
    LeonLi  
       2017-11-09 01:57:41 +08:00
    @FrankHB 戳帝球~
    FrankHB
        18
    FrankHB  
       2017-11-09 02:26:17 +08:00   ❤️ 1
    @LeonLi 要科普完的话,引用 Clause 3 和 Clause 4。

    你的说法有个会引起误会的地方,实际情况是 size_t 就是法定的 sizeof 表达式求值结果的无符号整数类型(某种意义上这就是 size_t 的首要意义),C 编译器改变不了这个决定;允许实现变更的是 size_t 和其它整数类型的关系。

    嘛,VLA 在 C11 算是 optional 了,不过还有-std=gnu11 之类的垫着就是了……
    q397064399
        19
    q397064399  
       2017-11-09 07:27:07 +08:00
    @congeec #3 好顶赞,,c 语言的古怪 声明 确实太烧脑了
    q397064399
        20
    q397064399  
       2017-11-09 07:29:44 +08:00
    @congeec #3 ** 意味着是 指针的指针, 当前指针指向了一个内存地址,,然后另一个指针里面的内容 就是当前指针的内存地址,我理解的对吗? 这样有什么 意义么?
    n2l
        21
    n2l  
       2017-11-09 08:48:17 +08:00 via iPhone
    指向字符串的指针数组竟然错误?这网站准不准啊?
    https://i.loli.net/2017/11/09/5a03a585b80d3.png
    n2l
        22
    n2l  
       2017-11-09 08:51:28 +08:00 via iPhone
    指向字符串的指针数组正确显示了,重新输入一遍就好了。
    limhiaoing
        23
    limhiaoing  
       2017-11-09 09:01:46 +08:00 via iPhone   ❤️ 1
    要看上下文的
    变量声明的时候是数组
    作为函数参数的时候是指针
    smol
        24
    smol  
       2017-11-09 09:34:13 +08:00   ❤️ 2
    是数组,数值的元素是 int**。int* a[4]是指针数组,int (*p)[4]才是指向数组的指针 ;类推过来,int** a[4]也是数组。

    不过十分讨厌这样的用法,问这个的题就像孔乙己问茴字的四种写法一样。除非有意炫耀,实际代码中正常人不会这么用的,真的需要多重指针(大多是仅用 C 的场合),也会借用 typedef 进行明确声明。

    C/C++这样晦涩的语法太多,标准委员会的一帮古董也是脑回路有问题,还在不断添加新的语法特性,也是够了。
    congeec
        25
    congeec  
       2017-11-09 09:46:12 +08:00
    @q397064399 想想一个函数要修改指针本身的值,怎么修改?

    #include <stdio.h>
    #include <assert.h>

    void fun(int **p) {
    ....*p = (int *)0x01;
    }

    int main() {
    ....int *p = NULL;
    ....fun(&p);
    ....assert((unsigned long)p == 0x01);
    ....return 0;
    }
    congeec
        26
    congeec  
       2017-11-09 09:48:48 +08:00
    @smol
    委员会:我可以添加,你可以不用
    程序员面试的时候:MMP
    picone
        27
    picone  
       2017-11-09 09:51:29 +08:00   ❤️ 1
    数组就是指针
    你可以理解成[]只是一个操作符,本质上还是指针
    FrankHB
        28
    FrankHB  
       2017-11-09 11:44:07 +08:00   ❤️ 2
    @smol 不要瞎扔锅,这就是 DMR 那阵子搞出来的古董,哪来的委员会。

    当然 BS 想甩掉而引入 trailing-return-type 又不敢扔掉 C 兼容搞得整体更复杂是另一回事——但是好歹比 Java 从来就没正经打算兼容还瞎倒腾高尚那么点。

    @picone 立刻停止你平成的扯蛋行为。
    lianz
        29
    lianz  
       2017-11-09 11:59:04 +08:00
    @LeonLi && @XiaoxiaoPu

    1 数组名是不允许重复赋值的,指针可以
    => 你可以把数组看成 const 指针


    2 sizeof 问题:
    => sizeof 才是真正的语法糖,编译器根据类型信息帮你计算出大小而已。
    enenaaa
        30
    enenaaa  
       2017-11-09 11:59:13 +08:00
    因为[]优先级比*高, 所以是数组。
    picone
        31
    picone  
       2017-11-09 12:03:26 +08:00
    @FrankHB 只是理解问题而已, 用起来不一样
    语言本身能实现功能就可以
    tabris17
        32
    tabris17  
       2017-11-09 12:10:34 +08:00
    数组不就是指针么,滑稽
    Icarooooos
        33
    Icarooooos  
       2017-11-09 12:11:18 +08:00 via Android
    是数组,推荐看《 C 专家编程》,里面讲的很详细。
    RLib
        34
    RLib  
       2017-11-09 12:15:46 +08:00
    只能说数组能隐式弱化成指针, 相反指针转数组只能显式转换
    XiaoxiaoPu
        35
    XiaoxiaoPu  
       2017-11-09 12:32:59 +08:00 via iPhone
    看到大神解释的很清楚了之后,还有人一本正经的说数组就是指针,真是尴尬癌都犯了。
    dangyuluo
        36
    dangyuluo  
       2017-11-09 12:37:36 +08:00
    @tabris17 数组怎么能是指针。
    FrankHB
        37
    FrankHB  
       2017-11-09 13:06:08 +08:00   ❤️ 1
    @lianz 又是个不知道基础知识点到连左值概念都没还强答的。老实重修。
    []可是直接在 spec 用语义规则钦定等价性了的,sizeof 还能强行语法糖……不用糖的替代语法呢?

    @picone 你能确保什么叫理解问题?在哪里理解?确定要理解的问题是什么了?
    问题明白挂着“ C/C++/Obj-C ”,足以判断你的回答的主要成分显然就是胡扯。
    排除这些内容,“用起来不一样”……不是我要找你茬,且不说哪里不一样,你可曾提到“用”?
    picone
        38
    picone  
       2017-11-09 13:26:46 +08:00
    @FrankHB 申请数组的时候返回的不就是指针咯
    只是为了方便楼主理解.

    指针是内存地址, 数组也是内存地址, 只是操作不同, 最终还是能定位到指定的内存地址
    jiang1234321
        39
    jiang1234321  
       2017-11-09 14:01:30 +08:00
    @congeec #3 什么时候需要从左往右读,什么时候从右往左读呢?
    msg7086
        40
    msg7086  
       2017-11-09 14:40:12 +08:00
    @picone 数组不是内存地址吧。只是数组的变量名指向了数组的首地址吧。
    申请数组,你说的是 new 么,那个返回的是数组的首地址,数组首地址和数组不是一个概念吧。
    LeonLi
        41
    LeonLi  
       2017-11-09 14:41:08 +08:00
    @lianz
    我觉得不了解 C 语言就不要硬拗了吧,你说了半天都是你自己所谓的理解,自己去翻一下 C 语言标准看看里面怎么规定的很难么?
    fcten
        42
    fcten  
       2017-11-09 14:52:41 +08:00   ❤️ 2
    说数组和指针是同一个东西的……是不是觉得手机和手机号也是同一个东西?
    picone
        43
    picone  
       2017-11-09 15:14:16 +08:00
    @msg7086 额..是的. 数组代表的是它的内存的首地址. 最终数组访问跟直接用指针访问,最后都是同一块的数据, 只是操作的形式不同
    crazyneo
        44
    crazyneo  
       2017-11-09 15:43:03 +08:00
    @picone 不明白数组和指针区别的,也就基本告别左右值和模板参数类型推演以及之后一连串的特性了,读一读 essential c 也行,不是你理解的那么回事儿。
    lany
        45
    lany  
       2017-11-09 16:02:06 +08:00
    这让我想起了一个好玩的东西。
    char* p1
    char** p2
    char*** p3
    char**** p4
    char***** p5
    zbcwilliam
        46
    zbcwilliam  
       2017-11-09 16:10:01 +08:00   ❤️ 1
    数组;数组元素是指向指针的指针
    FrankHB
        47
    FrankHB  
       2017-11-09 18:56:56 +08:00
    @picone 你就这么急着强行加戏,是嫌 LZ “理解”的还不够多不够混乱?
    我有些好奇你是怎么把“指针是内存地址,数组也是内存地址”“数组代表的是它的内存的首地址”这类无中生有的笑话大言不惭拿来帮助别人理解的。
    Pyjamas
        48
    Pyjamas  
       2017-11-09 19:00:59 +08:00
    数组是 decay 到指针的,并不和指针等价

    https://stackoverflow.com/questions/1461432/what-is-array-decaying
    Pyjamas
        49
    Pyjamas  
       2017-11-09 19:06:41 +08:00
    > 指针是内存地址, 数组也是内存地址, 只是操作不同, 最终还是能定位到指定的内存地址

    @picone 你的说法其实可以套用到,int 也是二进制数据,float 也是二进制数据,只是操作方式(解释方式)不同,有什么区别吗? 传值也是传数据,传引用也是传数据,只是数据的地址不同,有什么区别吗?
    Pyjamas
        50
    Pyjamas  
       2017-11-09 19:09:40 +08:00
    @Pyjamas
    > 传值也是传数据,传引用也是传数据,只是数据的地址不同,有什么区别吗?

    改一下:传值也是传数据,传引用也是传数据,只是操作的形式不同,最终还是能定位到指定的内存地址,所以传值=传引用
    picone
        51
    picone  
       2017-11-09 20:34:27 +08:00
    @FrankHB #47 好吧,那你说一下 指针放的不是内存地址是啥, 数组的变量名不是内存首地址又是啥, 或者给个传送门? 直接喷人没意思
    picone
        52
    picone  
       2017-11-09 20:36:05 +08:00
    @Pyjamas #49 嗯 是的.. 这么说是不同的。谢谢指教。
    nullcc
        53
    nullcc  
       2017-11-09 22:06:13 +08:00
    有一种笨办法,一层层拆开来看:

    int **p[10],p 是什么?

    1. **p[10]是一个 int
    2. *p[10]是一个 int 型指针
    3. p[10]是一个指向 int 型指针的指针
    4. p 是一个指向 int 型指针的指针的数组
    lrxiao
        54
    lrxiao  
       2017-11-10 00:38:01 +08:00
    这问题居然能引来地球。。
    msg7086
        55
    msg7086  
       2017-11-10 01:16:46 +08:00
    @picone
    比如说吧。
    int a[5];
    这里你问 a 是个什么东西 —— a 是个数组。
    你问 a 作为常量读出来的值是多少 —— 值是 a 的首地址。

    所有的变量到最后都是内存里的一小块数据,你也不能说整数就是浮点数吧。
    Mitt
        56
    Mitt  
       2017-11-10 02:13:19 +08:00
    关于 sizeof
    源码和结果:
    https://imgur.com/Uhjn72f

    汇编代码:
    https://imgur.com/DAqz7be

    另: 上面自以为是的家伙已 Block
    Mitt
        57
    Mitt  
       2017-11-10 02:14:01 +08:00

    ryd994
        58
    ryd994  
       2017-11-10 06:46:57 +08:00 via Android
    @Mitt 你这个例子不能证明任何问题
    他们说的是 VLA 的情况
    我也可以说是编译器帮你把常量优化了
    你要不要试试 1-100 求和?编译器能给你直接优化成 printf("%d", 5050)
    chiu
        59
    chiu  
       2017-11-10 07:21:39 +08:00 via Android
    @lianz 但他们类型不一样
    smol
        60
    smol  
       2017-11-10 09:36:12 +08:00
    @FrankHB 倒不是甩锅给委员会,这个问题跟他们也没毛关系。只是感叹一下委员会还在不断加新的语法特性,C/C++本身就是一把容易伤手的电锯了,他们还要给这把电锯增加钻头,安装链条。是的,新的电锯可以更省力,更全能,但是也更容易伤着人了。
    afpro
        61
    afpro  
       2017-11-10 10:21:13 +08:00
    楼上都在扯什么鬼啊 不能正常回答楼主问题咩
    这种情况类型判断顺序是 从右到左 从里到外
    int *p[10] 就是 [10]确定是数组 *确定数组里是指针 int 确定指针指向 int
    如果想反过来声明一个指向数组的指针 就用括号 int (*p)[10]
    Mitt
        62
    Mitt  
       2017-11-10 10:35:44 +08:00
    @ryd994 这不是优化 而是可以推导的, 就像上面说的可变数组,也不过是编译器提前算好你可变数组的定义长度 然后再 * 你的数组长度而已 并且他只能分配到栈上而不是堆上, 也就是在编译时就已经确定的东西,sizeof 本身并不是一个函数,而是一种运算符,是属于编译器的 你可以看 sizeof 和 strlen 的区别 而且我们说 sizeof 之前的点是数组是不是指针这一说 而举例出的 sizeof
    ryd994
        63
    ryd994  
       2017-11-10 13:13:58 +08:00 via Android
    @Mitt 我觉得你根本没懂什么是 vla,vla 的大小是运行时决定的,和你在这里用的定长数组根本不是一回事。楼上说 vla 也不过是为了说明 sizeof 并不总是可推导的。
    zhicheng
        64
    zhicheng  
       2017-11-10 14:13:22 +08:00
    数组是数组,指针是指针,不要乱讲。数组指的是一段内存空间,指针是一段内存地址。

    struct foo1 {
    long bar[1];
    };

    struct foo2 {
    long *bar;
    };

    这两个结构体占的内存一样,可存储的东西很不一样。
    wwqgtxx
        65
    wwqgtxx  
       2017-11-11 16:07:21 +08:00 via iPhone
    @zhicheng 你这个代码在不同的平台上不一定内存占用的一样,因为 long*和 long[0]占用的内存并不永远是一样的
    zhicheng
        66
    zhicheng  
       2017-11-11 16:10:57 +08:00
    @wwqgtxx 我知道,但这不影响我的观点。
    wwqgtxx
        67
    wwqgtxx  
       2017-11-11 16:13:28 +08:00 via iPhone
    @ryd994 个人感觉 vla 算是对 sizeof 的一个特例(也是有些编译器死活不愿意支持的原因),在排除 vla 的情况下,sizeof 的结果应该就是一个编译期间可以确定的参数。
    这个情况就类似于 c++的虚函数导致了需要编译器必须实现一个虚表来间接获得函数入口地址,而正常情况下函数的入口地址应该在编译或者链接期间是一个确定的值
    mn66
        68
    mn66  
       2018-03-10 09:48:14 +08:00 via Android
    这个应该是指向指针(*p [](是指向整形数组的))的指针,书上都有,应该把书好好看一下,在指针那节。所以 p 是指针。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3670 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 84ms · UTC 10:30 · PVG 18:30 · LAX 02:30 · JFK 05:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.