关于指针的若干思考

news/2024/7/20 12:52:55 标签: C, 指针, malloc, 内存管理, 嵌入式

字节

本文我们操作的最小的数据存储单位是字节

变量和变量名

1.当我们声明一个变量的时候,内存中就为我们开辟了一块空间,用于储存之后赋予变量的值,此时内存要解决两个问题:1. 存在哪里,2.开辟多大的空间,其实这两个问题在我们的语句中都有:其中变量名告诉我们把内存空间开辟到哪里,但是其实这个说法也不准确,准确来说是经历了以下步骤

  • 我们告诉了操作系统我们需要一块内存,这块内存的大小是int
  • 操作系统根据实际情况为我们分配了一块空间,这块空间可能是一个字节,也可能是很多个字节,具体的看我们的数据类型。这种分配可以如下图的自动分配,也可以是使用malloc函数手动分配
  • 当分配完成之后,操作系统会把分配空间的首地址和我们的变量名建立一个唯一映射,我们使用变量名就可以访问到这片内存空间中的值。
int i 

各种变量类型所占据的空间(按字节)

我的操作系统是64位的,各种数据类型所占据的空间(字节)如下

size of int is 4
size of long is 8
size of char is 1
size of float is 4
size of double is 8
size of int* is 8
size of char* is 8
size of int** is 8

注意所有不同数据类型的指针的大小都一样,无论是字符指针,整形指针,不同级数的指针大小也都一样,比如一级指针和二级指针,还有多级指针。但是为什么呢?因为我们说变量名是地址,而所有的变量,不管其内存多大,变量名都是等于该变量占据所有空间的首地址。所以所有指针的大小都是一样的

内存

内存是线性的,但是为了方便,我们通常把内存画成一个矩形,矩形的宽为一个字节,每一行都赋予一个唯一的地址,如图,每个小格子就是数据的最小存储单位,比特。每一行是一个字节。
在这里插入图片描述

定义变量和调用函数的内存行为

void swap(int* a,int* b){
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main(){
	int a = 1234 ;
	int b = 5678 ;
	swap(&a,&b);
}

在这里插入图片描述
以上是一个经典的交换函数,我在这里画了内存中的行为

	int a = 1234 ;
	int b = 5678 ;

这两条语句会在内存中开辟两个int空间(8字节),同时在对应的比特位填充数据。当我们调用swap函数的时候

swap(&a,&b);

我们在内存中开辟两个指针空间(8字节)用于接受传进来的地址。


从访问权限看函数传参

  1. 如果函数是值传递,那么我们对该值的权限只有,我们可以使用printf函数输出值,可以将值作为运算数进行运算
  2. 如果传的的是一个指针p,那么我们对该指针指向的对象*p具有读写的权限,我们可以改变其值,比如上面的swap函数就是地址传递

指针变量的赋值

指针之间的赋值相当于共享某空间的地址,参看下面代码

    int a=10;
    int* p = &a;
    int* q = p;
    printf("[*p] is %d,[&p] is %p\n",*p,p);
    printf("[*q] is %d,[&q] is %p\n",*q,q);

以上代码的输出是

[*p] is 10,[&p] is 0x7ffe0458c994
[*q] is 10,[&q] is 0x7ffe0458c994

如果要修改指针本身所指向的地方,我们需要使用二级指针,比如下面,指针p,q,r可以修改变量a的值,pp,qq,rr可以修改p,q,r指针的内容,换言之修改指针的指向,比如现在我想让指针p指向一个新的变量,那么我们需要操作二级指针
在这里插入图片描述

    int a= 10;
    int b= 20;
    int* p = &a;
    int** pp = &p;
    printf("%4s %4s  %14s %14s %14s%4s  %14s %14s %14s %14s\n","a","b","&a","&b","p","*p","&p","pp","*pp","&pp");
    
    printf("%4d %4d  %14p %14p %14p%4d  %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
    *p = 19;//change a
    printf("%4d %4d  %14p %14p %14p%4d  %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
    *pp = & b;//now p pointer to b
    printf("%4d %4d  %14p %14p %14p%4d  %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);
    *p = 30;
    printf("%4d %4d  %14p %14p %14p%4d  %14p %14p %14p %14p\n",a,b,&a,&b,p,*p,&p,pp,*pp,&pp);

输出内容是

ab&a&b
10200x7ffc9bd71c900x7ffc9bd71c94
19200x7ffc9bd71c900x7ffc9bd71c94
19200x7ffc9bd71c900x7ffc9bd71c94
19300x7ffc9bd71c900x7ffc9bd71c94
p*p&p
0x7ffc9bd71c90100x7ffc9bd71c98
0x7ffc9bd71c90190x7ffc9bd71c98
0x7ffc9bd71c94200x7ffc9bd71c98
0x7ffc9bd71c94300x7ffc9bd71c98
pp*pp&pp
0x7ffc9bd71c980x7ffc9bd71c900x7ffc9bd71ca0
0x7ffc9bd71c980x7ffc9bd71c900x7ffc9bd71ca0
0x7ffc9bd71c980x7ffc9bd71c940x7ffc9bd71ca0
0x7ffc9bd71c980x7ffc9bd71c940x7ffc9bd71ca0

malloc_120">说说链表的malloc

在刚开始写链表的时候,我一直纠结一个问题,那就是为什么我们需要在创建链表的时候传递二级指针,如下,我们定义了一个结构体,然后写了一个创建链表的函数

typedef struct Node{
    int data;
    char name[20];
    char nodeName[5];
    struct Node* next;
    struct Node* pre;
}Node;

void Create(Node** node){
    (*node) = (Node*) malloc(sizeof (Node));
    (*node)->next=NULL;
    (*node)->pre=NULL;
    (*node)->data=0;
    strncpy((*node)->nodeName,"Head",5);
    strncpy((*node)->name,"Head",5);
}

为了解决这个问题,我们需要说说malloc函数,malloc函数会分配一块内存空间,然后返回一个指针,而注意,在这个过程中,我们需要修改的是指针的值,而我们说过,在函数的参数传递中。

如果想要改变变量的值,需要传递指针
如果想要改变指针的值,需要传递指针指针,也就是二级指针

以下为了说明简便,我使用int**作为传递的指针类型,且看如下代码

#include "stdio.h"
#include "stdlib.h"

void Create(int** p){
    *p = (int*) malloc(sizeof (int ));
    *(*p)=20;
}

int main(){
    int* q;
    Create(&q);
    printf("%d",*q);
    
}

在这里插入图片描述

过程是这样的

  1. int* q,创建一个int指针,但是这个指针指向未知
  2. 通过函数传递二级指针,建立p和q的映射关系(*p=q)
  3. 通过*p来改变q的值,接受malloc的返回值
  4. 通过*(*p)来访问malloc的int空间,并且修改其值

如果把程序稍微改变一下,大家来观察下不同

#include "stdio.h"
#include "stdlib.h"

void Create(int** p){
	int* pointer = *p;
    pointer = (int*) malloc(sizeof (int ));
    *(pointer)=20;
}

int main(){
    int* q;
    Create(&q);
    printf("%d",*q);
    
}

看似小小的改变,但是产生了完全不同的结果

  1. 创建int指针,但是不知道指针指向哪里
  2. 通过函数传参建立了p和q的关系(p=*q)
  3. 创建了一个局部int指针pointer指向*q,也就是未知的空间
  4. 局部变量pointer接受malloc返回值
  5. 访问局部变量pointer指向的地方,赋值。
    在这里插入图片描述

我们发现其实q的值并没有发生变化,问题就出现在我们使用了int* pointer = *p;,我们需要修改的是p的值,所以我们需要传递p的指针,而p本身是一个int指针,所以我们需要传递二级int指针,而我们使用int* pointer = *p;语句,则是直接让我们的操作只能对q指向的位置空间操作,或者改变pointer的值,本操作直接令访问权限发生了变化。而且我们也不能在通过对pointer取地址运算重新操作*(Addrq),我们之后的一切操作都是针对局部变量pointer(见上图)。


降低指针权限,安全操作

看了上面,那时不是就说明int* pointer = *p;语句就一无是处呢?很显然不是,因为指针的可以任意访问,所以指针级数越高,也就越危险。我们传递二级指针是为了可以接受malloc的值,而之后的操作,绝大部分时间,都是读操作,所以我们可以尝试在使用二级指针接受malloc返回值之后,进行指针降级操作。如下:

#include "stdio.h"
#include "stdlib.h"

void Create(int** p){
    *p = (int*) malloc(sizeof (int ));
    int* pointer = *p;
    *(pointer)=20;
}

int main(){
    int* q;
    Create(&q);
    printf("%d",*q);
    
}

在这里插入图片描述


数组名和指针

在学习数组的时候我们知道,数组名其实就是指针,而对数组的下标索引操作,也可以使用指针的偏移来实现,比如

int a[10]={0};
//a[1] 和 *(a+1) 是等价的

函数指针

(未完待续。。。)


http://www.niftyadmin.cn/n/1689163.html

相关文章

C++ 面向对象基础

概述 写C的时候,我们是面向过程的,而在C中,我们则是面向对象,面向对象的思想可谓是一个革新,这也是使得构建缤纷的代码世界变得更加容易。本文中我想联系生活中的实际来说说和面向对象有关的事情。在此感谢阅读。 ne…

C++面向对象核心

类作为类的成员的变量类型 如下,Hair类就是Cat的一个成员的变量类型 class Hair{ public:string color;int length ;bool isSoft; };class Cat{ public:Hair catHair;int lengthOfLegs;string colorEyes; };int main() {Cat cat1;cat1.catHair.length20;cat1.leng…

C++面向对象高级

函数模板 模板的定义和模板的使用 #include "iostream" using namespace std; template <typename T> void myswap(T& a,T& b){T swap;swapa;ab;bswap; } int main(){int a 10;int b90;myswap(a,b);myswap<int>(a,b);cout<<a<<endl;…

Qt常用界面设计组件

概述 组件说明QLable标签&#xff1a;显示字符串QLineEdit单行输入框&#xff1a;显示和输入字符串SpinBox可以设置属性的输入框Slider滑动条ScrollBar卷滚条ProgressBar进度条Dial表盘LCDNunberLCD显示屏时间和定时器QComboBox和QPlainTextEditQListWidget和QToolButtonQTree…

Qt小项目(一):四则运算计算器

UI 代码 #include "widget.h" #include "ui_widget.h" #include "string" Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);connect(ui->zero,SIGNAL(clicked()),this,SLOT(on_all_clicked()))…

Qt信号详解

QAbstractButton clicked()&#xff1a;按下松开后执行pressed()&#xff1a;按下后执行released()&#xff1a;松开后执行clicked(bool checked)&#xff1a;需要选中checkable属性&#xff0c;每次点击checked的值都会切换&#xff0c;可以使用clicked()和查询ischecked属性…

Qt小项目(二):调色器

UI 代码 #include "widget.h" #include "ui_widget.h" #include "string" Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);connect(ui->red,SIGNAL(valueChanged(int)),this,SLOT(on_all_valu…

Qt小项目(三):定时器和日历

UI QTime和Qtimer类 类区别QTimer定时器类&#xff0c;当时间溢出&#xff08;记时超过定时区间&#xff09;的时候&#xff0c;就会发出timeout信号&#xff0c;触发槽函数QTime时间类&#xff0c;提供时分秒 概述以及注意的点 清零和记数可以获取lineEdit的值&#xff0c;动…