当前位置: 首页 > article >正文

指针与函数传递


title: 指针与函数传递
date: 2024-09-14 21:33:51
description: 函数传递多个元素
tags:

  • c language

skaiuijing

理解指针

很多人经常使用指针,看到这个标题可能不屑一顾。但笔者想说,把指针当作理所当然而不去探索它的本质,这是不对的。

先问个问题:指针是地址还是变量?(int *)a +1 增加了多少?

如果说 int a代表一个int大小的a变量,那么 int *a代表什么呢?答案是指向一个int 变量的指针变量a。

既然a有大小,是int,那么int *a有没有大小呢?答案是:肯定有!可能有人会回答一个字的大小,其实多少有点不严谨,指针的大小取决于具体的计算机架构和编译器,例如在常见的MCU-stm32上,一个指针的大小是32位,对应一个字。

所以说,指针的本质也是变量,(int *)a + 1,其实是加了对应一个指针的大小。

为什么指针能够修改内存

笔者定义一个结构体:

#include<stdio.h>

struct store{
    int a;
    int b;
};

int main()
{
    long long address;
    struct store *pointself;//此时pointself的值是随机的、无意义的
    pointself = &address;

    pointself->a = 10;
    pointself->b = 5;
    printf("address:%d,address-a: %d,address-b: %d\n",pointself,&(pointself->a),&(pointself->b));

    printf("store: %lld, a:%d ,b = %d\n",address,(int)address,(int)(*(((int *)&address) + 1 )));



}

程序的运行结果:

address:6422032,address-a: 6422032,address-b: 6422036
store: 21474836490, a:10 ,b = 5

x86-64为大端序,转为二进制就是:10100000000000000000000000000001010

假设架构为x86*64,那么内存布局如下:

变量a (4 bytes)b (4 bytes)
1010101
地址00000000 01100001 11111110 0001000000000000 01100001 11111110 00010100

让我们思考一下,poinself->a = 10时,到底发生了什么?

答案是:机器通过pointself这个指针,找到了这个具体地址对应的空间,然后存放了a的值。

反汇编如下:(程序已经被gcc优化得非常好了,可能不足以解释这个过程,但是可以作为参考)

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32
    mov     QWORD PTR [rbp-8], 0   //把这块区域作为存储address的空间,初始化为0
    lea     rax, [rbp-8]
    mov     QWORD PTR [rbp-16], rax //存储address的地址
    mov     DWORD PTR [rbp-8], 10	//存储a
    mov     DWORD PTR [rbp-4], 5	//存储b
    mov     rax, QWORD PTR [rbp-16]
    mov     rsi, rax
    lea     rdx, [rbp-8]
    lea     rcx, [rbp-4]
    mov     edi, OFFSET FLAT:.LC0
    mov     eax, 0
    call    printf
    mov     rax, QWORD PTR [rbp-16]
    mov     rax, QWORD PTR [rax]
    mov     esi, eax
    mov     rax, QWORD PTR [rbp-16]
    mov     eax, DWORD PTR [rax+4]
    mov     edi, OFFSET FLAT:.LC1
    mov     edx, eax
    mov     eax, 0
    call    printf
    mov     eax, 0
    leave
    ret

  1. 内存分配
    • sub rsp, 32 分配了 32 个字节的栈空间,用于存储 addresspointself
    • mov QWORD PTR [rbp-8], 0 初始化 address 为 0。
    • lea rax, [rbp-8] 计算 address 的地址并存储到寄存器 rax
    • mov QWORD PTR [rbp-16], raxaddress 的地址存储到 pointself
  2. 赋值操作
    • mov DWORD PTR [rbp-8], 10 将值 10 存储到 pointself->a 对应的内存位置。
    • mov DWORD PTR [rbp-4], 5 将值 5 存储到 pointself->b 对应的内存位置。
  3. 打印操作
    • mov rax, QWORD PTR [rbp-16]pointself 的值加载到寄存器 rax
    • mov rsi, raxrax 的值移动到 rsi,作为 printf 的参数。
    • lea rdx, [rbp-8] 计算 pointself->a 的地址并存储到 rdx
    • lea rcx, [rbp-4] 计算 pointself->b 的地址并存储到 rcx
    • mov edi, OFFSET FLAT:.LC0 将格式字符串的地址加载到 edi
    • call printf 调用 printf 函数。
    • mov rax, QWORD PTR [rbp-16]pointself 的值加载到寄存器 rax
    • mov rax, QWORD PTR [rax]address 的值加载到寄存器 rax
    • mov esi, eaxeax 的值移动到 esi,作为 printf 的参数。
    • mov rax, QWORD PTR [rbp-16]pointself 的值加载到寄存器 rax
    • mov eax, DWORD PTR [rax+4]pointself->b 的值加载到寄存器 eax
    • mov edi, OFFSET FLAT:.LC1 将格式字符串的地址加载到 edi
    • mov edx, eaxeax 的值移动到 edx,作为 printf 的参数。
    • call printf 调用 printf 函数。

现在你是否明白为什么我们在使用指针时,常常要malloc?其实就是在给指针变量赋一个有意义的值,不然当我们使用->时,计算机会发现这块空间存储了莫名其妙的东西,或者是这块空间压根不存在。

既然理解了指针,那么该使用它了。

在使用函数时,我们常常使用return传递某些变量,但是,有时候我们希望函数能够传递多个元素,这时有什么好办法呢?

现在,是时候看看指针的伟大之处了。

1.使用指针修改对应地址指向的值

#include <stdio.h>

void getTwoValues(int *x, int *y) {
    *x = 10;
    *y = 20;
}

int main() {
    int a, b;
    
    getTwoValues(&a, &b);
    
    printf("Returned values: a = %d, b = %d\n", a, b);
    
    return 0;
}

2.返回指向数组的指针

#include <stdio.h>

int* getTwoValues() {
    static int values[2];  // Static array so it persists after the function returns
    values[0] = 5;
    values[1] = 15;
    return values;
}

int main() {
    int *values = getTwoValues();
    
    printf("Returned values: values[0] = %d, values[1] = %d\n", values[0], values[1]);
    
    return 0;
}

3.返回指向结构体的指针

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

struct TwoValues {
    int val1;
    int val2;
};


struct TwoValues* getTwoValues() {
    struct TwoValues *result = (struct TwoValues *)malloc(sizeof(struct TwoValues));
    result->val1 = 50;
    result->val2 = 60;
    return result;
}

int main() {
    struct TwoValues *values = getTwoValues();
    
    printf("Returned values: val1 = %d, val2 = %d\n", values->val1, values->val2);
    
    free(values); 
    
    return 0;
}

这里解释一下,定义变量,其实就是开辟一片内存空间。我们一般在函数中定义的变量,都是局部变量,被存储在栈中,而malloc开辟的空间,是在堆中。栈的空间在函数结束后会被回收,而堆不会,必须手动清理,否则它永远存在,并且会造成内存溢出等问题。所以,我们可以使用动态内存分配来存储变量的值。

当然,你要是对地址、空间、内存这些东西感到烦躁,也有别的方法。不一定只能使用指针。

直接使用结构体

#include <stdio.h>

struct TwoValues {
    int val1;
    int val2;
};

struct TwoValues getTwoValues() {
    struct TwoValues result;
    result.val1 = 100;
    result.val2 = 200;
    return result;
}

int main() {
    struct TwoValues values = getTwoValues();
    
    printf("Returned values: val1 = %d, val2 = %d\n", values.val1, values.val2);
    
    return 0;
}


http://www.kler.cn/news/305230.html

相关文章:

  • C++速通LeetCode简单第12题-二叉树的直径
  • 深度学习-目标检测(四)-Faster R-CNN
  • C#实现串口中继
  • 不废话简单易懂的Selenium 页面操作与切换
  • Python实现一个简单的爬虫程序(爬取图片)
  • postgresql 导出CSV格式数据
  • 电脑连手机热点,上不了网
  • CSS 响应式设计(补充)——WEB开发系列36
  • [数据集][图像分类]痤疮严重程度分级分类数据集999张3类别
  • QT学习第五天
  • mysql workbench 如何访问远程数据库
  • 快手手撕 力扣2487 从链表中移除节点 单调栈 递归
  • HTTP 请求方式`application/x-www-form-urlencoded` 与 `application/json` 怎么用?有什么区别?
  • C++编译环境(IDE)推荐及安装
  • 美国硅谷大带宽服务器带宽堵塞解决方案
  • Oracle发邮件功能:设置的步骤与注意事项?
  • 【Oracle】TIMESTAMP类型时间计算时间差
  • SprinBoot+Vue宠物共享平台的设计与实现
  • 性能诊断的方法(五):架构和业务诊断
  • 前端下载word、excel文件的两种方法
  • [数据集汇总]智慧交通-铁路相关数据集汇总
  • dedecms靶场(四种webshell姿势)
  • JMeter 入门之远程启动,服务模式,多机联测,负载均衡测试
  • 基于python+django+vue+MySQL的酒店推荐系统
  • Golang | Leetcode Golang题解之第396题旋转函数
  • 数据结构C //线性表(链表)ADT结构及相关函数
  • Qt与MQTT交互通信
  • 探索音视频SDK的双重核心:客户端与服务端的协同作用
  • 裸金属服务器与云服务器的区别有哪些?
  • 选择合适的工业制造营销代理机构:应关注哪些方面