BeginMan blog

C结构体的种种

一. 结构体变量定义的三种方法

方式1(推荐):用typedef为已存在的结构体类型定义新名字,然后用新名字定义变量名

typedef struct student {
    int num;
    // ...
} STUD;

STUD student1, student2;

方式2:struct VarName完整的写出结构体的类型定义并声明变量

struct student {
    int num;
    // ...
} student1, student2;

struct student student3;

方式3:不定义类型,只定义变量

struct {
    int num;
    // ...
} student1, student2;
// 不能再定义student3了

二. 内存对其

如下一个结构体来看下sizeof打印。

struct {
    char c;
    int i;
} s;

int main()
{
    s.c = 'a';
    s.i = 0x0a0b0c0d;
    printf("%lu\n", sizeof(s)); 
    return 0;
}

运行下打印的是8。来gdb调试看下。

(gdb) p s
$7 = {c = 97 'a', i = 168496141}
(gdb) p &s
$8 = (struct {...} *) 0x100001018 <s>
(gdb) p &(s.c)
$9 = 0x100001018 <s> "a"
(gdb) p &(s.i)
$10 = (int *) 0x10000101c <s+4>

(gdb) x/16b &s
0x100001018 <s>:    97  0   0   0   13  12  11  10
(gdb) x/16b &s->c
0x100001018 <s>:    97  0   0   0   13  12  11  10
(gdb) x/16b &s->i
0x10000101c <s+4>:  13  12  11  10  0   0   0   0

i 变量被对齐到一个4 倍整数的地址上,在内存上,它并不是紧挨着变量c。这种现象叫做内存对齐,对齐以后的地址通常都是2 或4 的倍数。这种对齐最终会造成结构体s 内部char 和int 之间有一个“空洞(hole)”。

这种对齐的目的是使处理器能够更快速地进行寻址,以便执行的速度更快。这是一种以空间换时间的策略,虽然有点浪费空间,但是这样查找起来很快。

C函数和指针

这节是看《C语言点滴》关于指针的总结笔记📒。

一. 用指针类型作参数形参

利用函数中的指针形参,可以改变传入函数的实参的值。

这个是指针经典用法之一,不累述了。赵岩《C语言点滴》可真是一点一滴,不过指针这块讲解的很不错,比如下面的例子,想改变p的值。

版本1.指针做形参

void f(int *ptr)
{
    ptr = (int *)malloc(sizeof(int));
    *ptr = 999;
}

int main(int argc, char **argv)
{
    int *p, k = 5;
    p = &k;
    f(p);
    printf("%d\n", *p);

}

这是一段高危代码,不仅没输出999反而内存泄漏。

ps:其实呢,如果我们在f函数中不动态分配内存的话,这段代码是没问题的。

因为函数调用遵循单向值传递,所以,进入函数时,p 把自身保存的地址传递给ptr,它们同时指向k。在函数f中ptr 指向了malloc 申请的一块内存后,当程序退出,ptr 因为是局部变量,在栈上定义,所以伴随着函数出栈而消失。这里带来了两个问题,第一,指针p 的指向并没有被改变;第二,malloc 申请的内存由于没有任何指针指向,所以不能利用free 来释放,造成了内存的泄露。”

解决方案:

  1. 利用函数返回值
  2. 利用指向指针类型的指针
// 1. 利用函数返回值
int *f()
{
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 999;
    return ptr;
}

int main(int argc, char **argv)
{
    int *p, k = 5;
    p = &k;
    printf("%d\n", *p);
    p = f();
    printf("%d\n", *p);

    free(p);    // 避免内存泄漏

}

// 2. 利用指向指针类型的指针
void f(int **ptr)
{
    *ptr = (int *)malloc(sizeof(int));
    **ptr = 999;
}

int main(int argc, char **argv)
{
    int *p, k = 5;
    p = &k;
    printf("%d\n", *p);
    f(&p);  // 传入的是指针变量的地址
    printf("%d\n", *p);

    free(p);    // 避免内存泄漏

}

二. 函数返回指针类型

常见错误:

char* func(int i)
{
    char str[100];    // 大小不确定
    return str;   // error 
}

函数中数组str定义在栈上,所以str 会随着函数的结束而消失,虽然通过函数返回了一个指针,但是指向一个消失变量的指针还有什么意义呢?

不完美的解决方案:

利用malloc 函数从堆申请出一片内存,这块内存不会伴随着函数的退出而消失。所以使用完这个函数后,我们必须调用free 函数来释放内存。

char* func(int i)
{
    char *str = (char *) malloc(100);
    return str;
}

这个解决方案之所以不完美是因为我们还要free来释放内存.

更好的接口定义:

允许函数传入一个地址。当使用这个函数时,你可以通过指针p 传入一个数组,或者传入一个malloc 函数分配的内存。如果你使用malloc 函数分配的内存,你应该有责任使用free 来释放这片内存。另外,这个函数还把传入的地址返回,这样这个函数就可以直接用在表达式中

char* func(int i, char *p) 
{
    // 具体实现...
    return p;
}

char a[100];    // 或 char *a = malloc(n);
printf("%s", func(1, a));

三. 函数指针

指向一个函数,函数指针最常见的一个用处就是:”回调函数”

函数指针的声明和数组指针有点类似:

int (*p)[3];    // 数组指针,指向的是一个数组
int (pf)();     // 函数指针,指向的就是一个函数,这个函数返回一个整数

pf=func;       // 取地址运算符 来让函数指针pf 指向一个特定的函数func

// 用如下方式皆可调用func这个函数
pf();
(*pf)();    

如下实例:

int f(int i, int j)
{
    return i+j;
}

int main(int argc, char **argv)
{
    int (*fp)();
    fp = &f;
    int m = (*fp)(1, 2);
    int n = fp(1, 2);
    printf("%d, %d", m, n);

}

下面实现一个支持多种类型的冒泡排序算法:

#include <stdio.h>
#include <stdlib.h>

/*
 * 第一个参数为要排序的数组
 * 第二个参数为数组内的数量
 * 第三个参数为数组元素类型所占的空间字节大小
 * 第四个参数为函数指针
 */
void sort(void *data,
          int n,
          int type_size,
          int (*ptr)(const void *, const void *))
{
    int i, j;
    void *temp = malloc(type_size);
    void *addr_1, *addr_2;
    for (i = 0; i < n - 1; ++i) {
        for (j = i+1; j < n; ++j) {
            addr_1 = data + i * type_size;
            addr_2 = data + j * type_size;
            if (ptr(addr_1, addr_2) > 0) {
                memcpy(temp, addr_1, type_size);
                memcpy(addr_1, addr_2, type_size);
                memcpy(addr_2, temp, type_size);
            }
        }
    }
    free(temp);
}

/*
 * 浮点数比较程序
 */
int comp_double(const void *a, const void *b)
{
    if (*(double *)a - *(double *)b > 0) {
        return 1;
    } else if (*(double *)a - *(double *)b < 0.001) {
        return 0;
    }
    return -1;
}

/*
 * 整数比较
 */
int comp_int(const void *a, const void *b)
{
    return *(int *)a > *(int *)b;
}


int main(int argc, char **argv)
{
    double n[] = {19.23, 0.32, 88.32, 20.31, 2.193};
    sort(n, 5, sizeof(double), &comp_double);
    for (int i = 0; i <5; ++i) {
        printf("%10.3f", n[i]);
    }

    printf("\n");
    
    int m[] = {2, 3, 1, 8, 3, 4};
    sort(m, 6, sizeof(int), &comp_int);
    for (int i = 0; i <5; ++i) {
        printf("%10d", m[i]);
    }

    printf("\n");
}

标准库中有qsort 函数,采用快速排序算法,比冒泡排序效率要高。qsort 函数采用的也是这种基于回调函数的方法。

(完)~

wks-05: 入门简单的Linux系统扫描技术及安全防范

这是关于慕课网Linux系统扫描技术及安全防范的学习,虽然讲的比较浅但是很入门,最近比较忙,『一周技术』类博客一直滞后,没什么亮点,暂且忙完这阵子吧。

网络入侵方式: 踩点-网络扫描-查点-提权等。

一. 主机扫描

1.1 fping

fping并行发送,批量给目标主机ping请求,测试主机存活情况。fping -h来查看具体使用。可参看比ping更强大的fping这篇博客进一步学习。

常用参数:

  • -a 只显示存活主机(-u相反)
  • -g 支持主机段方式,如fping 192.168.1.1 192.168.1.255 或 192.168.1.1/24
  • -f 通过文件读取

如:

$ fping -g 192.168.1.1/24
192.168.1.1 is alive
192.168.1.149 is alive
192.168.1.2 is alive
192.168.1.104: error while sending ping: No route to host
192.168.1.3 is unreachable
.....

1.2 hping

hping支持使用TCP/IP数据包组装,分析工具,对一些屏蔽掉ICMP包的主机,pingfping就没辙了,那么可用hping

对目标端口发起tcp探测: -p端口, -S 设置TCP SYN包。还可以伪造来源IP,模拟Ddos攻击,加-a伪造IP地址。

来测试下,在一台主机上(简称主机B)查看正监听的TCP端口:

$ netstat -ltnp
tcp        0      0 0.0.0.0:8380         0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:29362               0.0.0.0:*                   LISTEN

在主机A 通过 hping 来测试下:

$ sudo hping -p 80 -S x.x.x.x
HPING x.x.x.x (en0 x.x.x.x): S set, 40 headers + 0 data bytes
len=44 ip=x.x.x.x ttl=50 DF id=0 sport=80 flags=SA seq=0 win=14600 rtt=417.8 ms
len=44 ip=x.x.x.x ttl=50 DF id=0 sport=80 flags=SA seq=1 win=14600 rtt=335.6 ms
...

其实ping也可,那么现在让主机B关闭ICMP,不允许ICMP协议进行连接, 则在主机B写入内核一个参数使其生效:

# 主机B
$ sysctl -w net.ipv4.icmp_echo_ignore_all=1
net.ipv4.icmp_echo_ignore_all = 1

那么现在就ping不通了

# 主机A
ping x.x.x.x
PING x.x.x.x (x.x.x.x): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
...

但是我们的hping依然可用,它通过TCP形式来ping。

# 主机A
# 使用TCP探测方式
$ sudo hping -S x.x.x.x

HPING x.x.x.x (en0 x.x.x.x): S set, 40 headers + 0 data bytes
len=40 ip=x.x.x.x ttl=50 DF id=0 sport=0 flags=RA seq=0 win=0 rtt=196.5 ms
len=40 ip=x.x.x.x ttl=50 DF id=0 sport=0 flags=RA seq=1 win=0 rtt=202.2 ms
^C
--- x.x.x.x hping statistic ---
3 packets tramitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 196.5/206.0/219.3 ms

# 通过默认ICMP依然ping不通
$ sudo hping x.x.x.x

HPING x.x.x.x (en0 x.x.x.x): NO FLAGS are set, 40 headers + 0 data bytes
^C
--- x.x.x.x hping statistic ---
5 packets tramitted, 0 packets received, 100% packet loss
round-trip min/avg/max = 0.0/0.0/0.0 ms

二. 路由扫描

查询一个主机到另一个主机经过路由的跳数和数据延迟等,常用工具traceroute,mtr.

traceroute主要参数:

  • 默认使用UDP协议(30000以上端口)
  • -T 使用TCP ,-p 指定端口
  • -I 使用ICMP

如下traceroute下慕课网网站。

# -n 不显示主机名
$ traceroute -n jianshu.com

关于mtr则是比较直观,连通性。在我的Centos下还么有这个工具,可通过yum install mtr 安装。

$ mtr www.imooc.com

一直在变化,很直观,如下图。

三. 批量服务扫描

目的:

  • 批量主机存活扫描
  • 针对主机服务扫描

命令: nmap, ncat

# 批量主机扫描
$ nmap -sP 192.168.1.1/24

Starting Nmap 7.12 ( https://nmap.org ) at 2016-12-14 23:01 CST
Nmap scan report for 192.168.1.1
Host is up (0.0032s latency).
Nmap scan report for 192.168.1.4
Host is up (0.096s latency).
Nmap scan report for 192.168.1.149
Host is up (0.00066s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 2.97 seconds

端口扫描

# -p 0-n 指定0~n端口范围,默认是0-1024,同时包含一些常用的如80**等
$ sudo nmap -sS -p 0-n ip 或域名

四. linux防范恶意扫描安全策略

常用的攻击方法:

  • SYN 攻击 SYN flood洪水攻击
  • DDOS 拒绝服务攻击
  • 恶意扫描攻击(暂无办法解决)

SYN攻击属于DOS攻击的一种,它利用TCP协议缺陷,通过发送大量的半连接请求,耗费CPU和内存资源。SYN攻击除了能影响主机外,还可以危害路由器、防火墙等网络系统,事实上SYN攻击并不管目标是什么系统,只要这些系统打开TCP服务就可以实施。

解决方案:

  • 增加backlog大小
  • 调小重试次数
  • 发现此类找不到地址则拒绝三次握手,不再重试。

参数解释:

  1. tcp_max_syn_backlog是SYN队列的长度,加大SYN队列长度可以容纳更多等待连接的网络连接数
  2. tcp_syncooking是一个开关,是否打开syn cookie功能,该功能可以防止部分的SYN攻击
  3. tcp_synack_retries和tcp_syn_retries定义SYN的重试连接次数,将默认的参数减小来控制SYN连接次数

编辑完成之后使内核生效 sysctl -p

参考

慕课网:Linux系统扫描技术及安全防范 CentOS防止syn攻击

C内存分配与处理

唉哟我去,赵岩的《C语言点滴》错误太多了,不堪入目🙈啊。

一. C 数据类型一览

二. C 内存映象

存储
栈区 局部变量,返回值,形参等(短生命周期),自动分配和释放,快如闪电,但容量小.
堆区 动态(malloc/free),自己管理内存
静态区 全局变量,static
常量区 常量(不可变)
代码区 程序

“栈和堆是对向增长的,它们的容量并不是无限的,而是一定的。当使用递归函数的时候,如果调用的层数太深,栈会不断地增长,最后就会造成栈溢出(stack overflow)的错误。”

// 最简单的递归函数
// 这段程序运行不到一分钟就产生"栈溢出"异常而终止
void main()
{
    main();
}

程序中,变量位置的不同决定了其在内存中的映象:

int a = 4;              // 全局变量, 保存在静态存储器
static float f = 2.0f;  // 静态变量, 保存在静态存储器

int foo(int i)
{
    i++;        // 函数实参i, 保存在栈上
    return i;
}

int main(int argc, char **argv)
{
    int *p, k = 5;              // 局部变量 p,k保存在栈上
    char *pstr = "hello";       // hello字符串保存在常量存储区, pstr保存栈上
    char astr[10] = "hello";    // hello字符串以及变量astr都保存在栈上

    char *l = (char*)malloc(1); // 变量p,保存在栈上, p 指向的内存在堆上
    free(l);                    // 释放在堆上申请的内存

    int m = foo(k);
    printf("m=%d\n", m);
}

三.内存分配与释放

内存分配函数:malloccalloc, loc 读[loʊk], 内存释放free

注意分配的时候都没有数据类型的概念,分配的基本单位就是字节。如果你想分配10 个int 类型的数,不能写成malloc(10),而应该写成 malloc(sizeof(int) * 10)

分别声明如下:

// size 为需要分配的内存空间的大小,以字节(Byte)计, 并返回该空间的首地址
void	*malloc(size_t size);      

// 从堆上获得size * n的字节空间,并返回该空间的首地址
void	*calloc(size_t n, size_t size);

// 重新分配
void * realloc(void * p,int n);

// 由于形参为void指针,free函数可以接受任意类型的指针实参。
void	 free(void *);     

3.1 malloc and calloc and realloc

分配成功返回指向该内存的地址,失败则返回 NULL。

注意:函数的返回值类型是 void *void 类型明显有泛型的概念,用户需要把void 类型强制转换成需要的类型, void 并不是说没有返回值或者返回空指针,而是返回的指针类型未知。所以在使用 malloc() 时通常需要进行强制类型转换,将 void 指针转换成我们希望的类型,例如:

char *ptr = (char *)malloc(10);  // 分配10个字节的内存空间,用来存放字符

malloc与calloc的不同:

  • malloc函数分配得到的内存空间是未初始化的
  • 函数calloc 会将所分配的内存空间中的每一位都初始化为零。为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;指针类型的元素分配内存初始化为空指针NULL;实型数据分配内存,初始化为浮点型的零”
  • malloc可以配合memset模拟calloc
  • malloc速度快。
  • 如果分配内存并初始化,推荐用calloc, 快一些。

如下实例:

size_t size = sizeof(int);
int count = 1;

int *pm = (int *)malloc(count * size);
if (pm == NULL) exit(1);
printf("*pm=%d\n", *pm);

memset(pm, 0, count * size);    // 将pm指向的空间清0
printf("*pm=%d\n", *pm);

int *pc = calloc(count, size);
printf("*pc=%d\n", *pc);

free(pm);
free(pc);
pm = NULL;
pc = NULL;

realloc函数

void * realloc(void * p,int n);

其中,指针p必须为指向堆内存空间的指针,即由malloc函数、calloc函数或realloc函数分配空间的指针。realloc函数将指针p指向的内存块的大小改变为n字节。如果n小于或等于p之前指向的空间大小,那么。保持原有状态不变。如果n大于原来p之前指向的空间大小,那么,系统将重新为p从堆上分配一块大小为n的内存空间,同时,将原来指向空间的内容依次复制到新的内存空间上,p之前指向的空间被释放。relloc函数分配的空间也是未初始化的。

3.2 free

free函数只是释放指针指向的内容,而该指针仍然指向原来指向的地方,此时,指针为野指针,如果此时操作该指针会导致不可预期的错误。安全做法是:在使用free函数释放指针指向的空间之后,将指针的值置为NULL

free(p);
p = NULL;

小实验

来指出哪些地方会出错:

char *gp = "hello";     
char ga[] = "hello";   

char *f() {
    char *p = "hello"; 
    char a[] = "hello";
    p[0] = 'A';     // ?
    gp[0] = 'B';    // ?
    gp = a;         
    gp[0] = 'C';    // ?
    printf("gp=%s\n", gp);  // 请问这里输出什么?
    return a;       // ?
}

int main(int argc, char **argv)
{
    char *str = f();
    str[0] = 'D';   // ?
    ga[0] = 'E';    // ?
    // 请问输出什么?
    printf("gp=%s, str=%s, ga=%s\n", gp, str, ga);
    return 0;
}

假如这是一道面试题,那么考的就是内存映象,搞明白它们的存储位置就能解出这道题。

首先分析下程序,试图改变保存在常量存储区字符串的操作都是不合法的,因为gp 和p 指向的“hello”和“hello”保存在常量存储区

其次,数组ga 和a 的内容,或者保存在静态存储区,或者保存在栈上,它们都是独立的,有自己的存储空间,所以允许对数组的内容进行修改。但是,数组char a[]在栈上生成,当函数f 结束的时候,所有的局部变量都从栈中弹出而消失,所以数组char a[]和其内容都不再存在。这个时候,即使用str 得到了数组的地址,也是指向一个不存在的数组。所以str[0]=’D’;也是不允许的。

最后。指针gp 和p 所指向的内容不可修改,但是并不意味着指针本身的值、也就是指针的指向不可修改。我们完全可以写出gp=a 的语句,这个时候,gp 指向数组a,这样,gp[0]=’C’就是合法的了。看来,指针只是指向一个存储地点,不同的存储地点有不同的行为,所以,指针也就有了对应的特性,这些特性并不是指针带来的,而是由字符串处在不同的存储地点带来的。

看下面注释过的程序:

char *gp = "hello";     // 字符串hello 保存在常量存储区, 而指针gp保存在静态存储区
char ga[] = "hello";   // hello和数组ga保存在静态存储区

char *f() {
    char *p = "hello"; // hello保存在常量存储器, 指针p保存在栈上
    char a[] = "hello";// hello和数组a 保存在栈上
//    p[0] = 'A';   // runtime error -> bus error
//    gp[0] = 'B';  // runtime error -> bus error
    gp = a;
    gp[0] = 'C';    // OK
    printf("gp=%s\n", gp);
    return p;       // 把 a 改成 p, return p;
}

int main(int argc, char **argv)
{
    char *str = f();
//    str[0] = 'D'; // runtime error -> bus error
    ga[0] = 'E';    // ok
    printf("gp=%s, str=%s, ga=%s\n", gp, str, ga);
    return 0;
}

输出如下:

gp=Cello
gp=, str=hello, ga=Eello

个中原因就不说了,见上面。

参考

C语言的指针与数组种种关系

此篇为《C语言点滴》关于指针的总结

一. 指针与数组关系

  指针 数组
指针 指针指针 指针数组
数组 数组指针 数组数组

表1 指针与数组的组合

假设以int类型测试:

  • int **pp 定义了一个指向指针的指针(指针指针)
  • int *pa[5]定义了一个指针数组
  • int (*ap)[5]定义了一个数组指针
  • int aa[2][3]定义了一个数组数组,也就是一个二维数组
  指针 数组
指针 指针指针:int **pp 指针数组:int *pa[5]
数组 数组指针:int (*ap)[5] 数组数组:int aa[2][3]

表2:四个概念的定义

真理:

  1. 指针真理:一个xx 型指针应该指向一个xx 型地址。
  2. 数组真理:xx 型数组变量就是一个xx 型地址。

真理推导:

  • 根据真理1可知:一个指针型指针指向一个指针型地址;一个数组型指针指向一个数组型地址
  • 根据真理2可知:指针型数组变量就是一个指针型地址;数组型数组变量(二维数组)就是一个数组型地址。

综上推导:

  • 一个 指针型指针 可以指向一个 指针型数组变量, 上表第一行 pp = pa
  • 一个 数组型指针 可以指向一个 数组型数组(二维数组), 上表第二行 ap = aa
  指针变量 数组变量
char 类型 char *p char a[5]
指针类型 int **pp int *pa[5]
数组类型 int (*ap)[5] int aa[2][3]

表3 指针变量可指向对应数组变量

二. 指针指针、指针数组、数组指针

2.1 指针型指针

关于指针型指针,如:

int i = 5;
printf("i=%d, &i=%p\n", i, &i);

int *p = &i;
printf("*p=%d, p=%p, &p=%p\n", *p, p, &p);

int **pp = &p;
printf("*pp=%d, pp=%p, &pp=%p\n", **pp, pp, &pp);

打印输出:

i=5,    &i=0x004f5a6c
*p=5,   p=0x004f5a6c,   &p=0x004f5a5a
*pp=5,  pp=0x004f5a5a,  &pp=0x004f5a4c

每个变量,无论它保存的是什么,它本身也都有地址:

  • 指针变量p 保存的是变量i 的地址0x004f5a6c,但是p 本身的地址是0x004f5a5a。
  • 指针变量pp 中保存的是指针变量p 的地址0x004f5a5a。为了保存指针型变量p 的地址,我们必须定义一个指针型指针,那就是int **pp

如下图示:

图1:示意图

2.2 指针数组

对于一个指针数组,数组中保存的都是指针,数组名就是指针型地址。为了保存这种指针型地址,根据指针真理,我们自然应该用指针型指针了。

int *p[3],这是因为[]的优先级比*号高,所以int *p[3]会被编译器解释为int *(p[3])。这就是指针数组

见下:

char *a[] = {"人生苦短", "😁", "Python当歌", NULL};   // 指针数组
char **p;                                           // 指针指针
for (p = a; *p != NULL; p++) {
   printf("%s\n", *p);
}

打印输出:

人生苦短
😁
Python当歌

2.3 数组指针

如何定义一个数组型的指针?不能写成int *p[3],这是因为[]的优先级比*号高,所以int *p[3]会被编译器解释为int *(p[3])。这其实就是上面介绍过的指针数组,而不是我们要定义的数组指针。为了解决这个问题,我们用括号改变它的优先级,写成int (*p)[3]。这时(*p)是一个指针,指针指向的类型为int[3],这是一个一维数组型变量,符合我们的定义。

图2:对比一维和二维的数组指针实例

有了int (*p)[3]=array[2][3]的写法,我们对二维数组也可以利用指针来进行访问了,如下面所示:

  • p 即 &array[0]
    • 代表二维数组的首地址,第0 行的地址
    • 代表一维int 数组型变量int[]的地址
  • p+i 即 &array[i]
    • 代表第i 行的地址
    • 一维int 数组型变量int[]的地址
  • *(p+i)array[i]
    • 代表第i 行第0 列的地址
    • 一维int 数组型int[]变量,等价于int 类型的地址
  • *(p+i)+j&(array[i][j])
    • 代表第i 行第j 列的地址
    • int 类型的地址
  • *(*(p+i)+j )array[i][j]
    • 代表第i 行第j 列的元素
    • int 类型的值”

还是看代码测试吧:

int array[2][3] = {
       {101, 102, 103},
       {201, 202, 203}
};

int (*p)[3] = array;
printf("p=%p, &array[0]=%p\n", p, &array[0]);
printf(p == &array[0] ? "true\n" : "false\n");

/*
* out:
* p=0x7fff5cfa2540, &array[0]=0x7fff5cfa2540
* true
*/


printf("p+1=%p, &array[1]=%p\n", p+1, &array[1]);
printf(p+1 == &array[1] ? "true\n" : "false\n");

/*
* out:
* p+1=0x7fff5cfa254c, &array[1]=0x7fff5cfa254c
* true
*/

printf("*(p+1)=%p, array[1]=%p\n", *(p+1), array[1]);
printf(*(p+1) == array[1] ? "true\n" : "false\n");

/*
* out:
* *(p+1)=0x7fff5cfa254c, array[1]=0x7fff5cfa254c
* true
*/
printf("*(p+1)+2=%p, &(array[1][2])=%p\n", *(p+1)+2, &(array[1][2]));
printf(*(p+1)+2 == &(array[1][2]) ? "true\n" : "false\n");

/*
* out:
* *(p+1)+2=0x7fff5cfa2554, &(array[1][2])=0x7fff5cfa2554
* true
*/

printf("*(*(p+1)+2)=%d, array[1][2]=%d\n", *(*(p+1)+2), array[1][2]);
printf(*(*(p+1)+2) == array[1][2] ? "true\n" : "false\n");

/*
* out:
* *(*(p+1)+2)=203, array[1][2]=203
* true
*/

关键部分就是优先级, 如 () > [] > *

下面两篇文章基本搞定:

如果还不清楚,就看《深入理解C指针》这本不到200页的书。

(完)~