MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言指针间接访问与左值关系

2021-07-284.6k 阅读

C 语言指针间接访问与左值关系

指针间接访问基础

在 C 语言中,指针是一种特殊的变量类型,它存储的是另一个变量的内存地址。通过指针,我们可以间接访问存储在该地址中的数据,这就是指针的间接访问操作。这种操作是通过解引用运算符 * 来实现的。

例如,考虑以下代码:

#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;

    printf("通过变量直接访问 num: %d\n", num);
    printf("通过指针间接访问 num: %d\n", *ptr);

    return 0;
}

在这段代码中,我们首先声明了一个整型变量 num 并初始化为 10。然后,我们声明了一个指向 int 类型的指针 ptr,并将其初始化为 num 的地址,即 &num

当我们使用 *ptr 时,就是在对指针 ptr 进行间接访问,获取存储在 ptr 所指向地址中的值。在这个例子中,*ptrnum 所表示的值是相同的,都是 10。

左值概念解析

左值(lvalue)在 C 语言中有特定的含义。简单来说,左值是一个表示内存位置的表达式,并且可以出现在赋值运算符的左边(这也是“左值”这个名称的由来)。左值通常是变量,但也可以是某些具有可修改内存位置的表达式。

例如:

int a = 5;
a = 10;  // a 是左值,可以出现在赋值运算符左边

这里的变量 a 就是一个左值,因为它代表了一个特定的内存位置,我们可以对这个位置进行赋值操作。

并不是所有的表达式都是左值。例如,常量就不是左值:

5 = 10;  // 错误,5 是常量,不是左值

因为常量没有可修改的内存位置,不能出现在赋值运算符的左边。

指针间接访问与左值的关系

  1. 指针间接访问产生左值 当我们对指针进行间接访问时,*ptr 表达式通常是一个左值。这是因为 *ptr 表示的是指针 ptr 所指向的内存位置,而这个内存位置是可以被修改的(前提是该内存位置是可写的)。
#include <stdio.h>

int main() {
    int num1 = 10;
    int num2 = 20;
    int *ptr = &num1;

    *ptr = num2;  // *ptr 是左值,可以进行赋值操作
    printf("num1 的值: %d\n", num1);

    return 0;
}

在上述代码中,*ptr 是通过对指针 ptr 进行间接访问得到的。由于 ptr 指向 num1 的内存位置,*ptr 就代表了 num1 所占据的内存区域,所以可以对 *ptr 进行赋值操作,将 num2 的值赋给 num1

  1. 左值与指针间接访问的内存语义 从内存角度来看,指针间接访问和左值紧密相关。左值代表了内存中的一个可寻址位置,而指针间接访问则是通过指针找到这个可寻址位置。

假设我们有如下代码:

#include <stdio.h>

void modifyValue(int *p) {
    *p = 100;
}

int main() {
    int value = 50;
    int *ptr = &value;

    modifyValue(ptr);
    printf("修改后 value 的值: %d\n", value);

    return 0;
}

modifyValue 函数中,参数 p 是一个指针。当我们调用 modifyValue(ptr) 时,p 指向了 value 的内存位置。通过 *p 进行间接访问,*p 是一个左值,我们可以修改 *p,也就是修改 value 所占据的内存位置的值。

  1. 指针类型与左值特性 指针的类型决定了间接访问后的左值特性。例如,指向常量的指针,其间接访问结果虽然是一个左值,但不能通过这个左值修改所指向的内容。
#include <stdio.h>

int main() {
    const int num = 10;
    const int *ptr = &num;

    // *ptr = 20;  // 错误,不能通过指向常量的指针修改常量
    printf("num 的值: %d\n", *ptr);

    return 0;
}

这里,ptr 是一个指向常量 int 类型的指针。虽然 *ptr 仍然是一个左值(因为它表示一个内存位置),但由于其所指向的内容是常量,不能通过 *ptr 进行赋值修改。

  1. 数组与指针间接访问作为左值 在 C 语言中,数组名在大多数情况下会被隐式转换为指向数组首元素的指针。对这个指针进行间接访问也会产生左值。
#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;

    *ptr = 10;  // 等同于 arr[0] = 10;,*ptr 是左值
    printf("arr[0] 的值: %d\n", arr[0]);

    return 0;
}

在这个例子中,arr 是数组名,它被隐式转换为指向 arr[0] 的指针。ptr 指向了数组首元素,*ptr 就是 arr[0],是一个左值,可以进行赋值操作。

复杂指针结构与左值

  1. 多级指针与左值 多级指针是指指针指向的是另一个指针。例如,二级指针是指向指针的指针。当对多级指针进行间接访问时,同样会涉及到左值的概念。
#include <stdio.h>

int main() {
    int num = 10;
    int *ptr1 = &num;
    int **ptr2 = &ptr1;

    **ptr2 = 20;  // **ptr2 是左值,通过二级指针间接访问并修改 num
    printf("num 的值: %d\n", num);

    return 0;
}

在这段代码中,ptr2 是一个二级指针,它指向 ptr1*ptr2 得到的是 ptr1,而 **ptr2 就是对 ptr1 再进行一次间接访问,得到 num**ptr2 是一个左值,因为它代表了 num 所占据的内存位置,可以进行赋值操作。

  1. 指针数组与左值 指针数组是一个数组,其元素都是指针。当对指针数组中的元素进行间接访问时,也会产生左值。
#include <stdio.h>

int main() {
    int num1 = 10;
    int num2 = 20;
    int *ptrArr[2] = {&num1, &num2};

    *ptrArr[0] = 30;  // *ptrArr[0] 是左值,修改 num1 的值
    printf("num1 的值: %d\n", num1);

    return 0;
}

在这个例子中,ptrArr 是一个指针数组,ptrArr[0] 是指向 num1 的指针。*ptrArr[0] 就是对 ptrArr[0] 进行间接访问,代表了 num1 所占据的内存位置,是一个左值,可以进行赋值操作。

指针间接访问作为左值的限制

  1. 空指针与未初始化指针 如果指针是一个空指针(NULL)或者未初始化的指针,对其进行间接访问会导致未定义行为,并且此时的间接访问结果不能作为合法的左值。
#include <stdio.h>

int main() {
    int *ptr;  // 未初始化指针
    // *ptr = 10;  // 未定义行为,不能将未初始化指针的间接访问作为左值

    ptr = NULL;
    // *ptr = 20;  // 未定义行为,不能将空指针的间接访问作为左值

    return 0;
}

在上述代码中,对未初始化指针 ptr 和空指针 ptr 进行间接访问并试图作为左值进行赋值操作,这是不合法的,会导致程序出现未定义行为。

  1. 指针越界与左值 当指针指向的位置超出了合法的内存范围(指针越界),对其进行间接访问并作为左值进行操作同样会导致未定义行为。
#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr + 10;  // 指针越界

    // *ptr = 10;  // 未定义行为,指针越界后间接访问不能作为合法左值

    return 0;
}

在这个例子中,ptr 被设置为指向 arr 数组之外的内存位置,此时对 *ptr 进行赋值操作是未定义行为,因为 *ptr 不再代表一个合法的可写内存位置。

指针间接访问与左值在函数参数传递中的应用

  1. 传递指针以修改实参 在 C 语言中,通过传递指针给函数,可以在函数内部修改调用函数中的实参值。这是利用了指针间接访问产生左值的特性。
#include <stdio.h>

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

int main() {
    int num1 = 5;
    int num2 = 10;

    printf("交换前 num1: %d, num2: %d\n", num1, num2);
    swap(&num1, &num2);
    printf("交换后 num1: %d, num2: %d\n", num1, num2);

    return 0;
}

swap 函数中,参数 ab 是指针。通过 *a*b 进行间接访问,它们都是左值,可以在函数内部修改 num1num2 的值。

  1. 指针作为函数返回值与左值 函数可以返回指针,返回的指针所指向的内存位置可以通过间接访问作为左值。
#include <stdio.h>

int *findMax(int arr[], int size) {
    int *maxPtr = &arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > *maxPtr) {
            maxPtr = &arr[i];
        }
    }
    return maxPtr;
}

int main() {
    int arr[5] = {3, 7, 1, 9, 4};
    int *maxPtr = findMax(arr, 5);

    *maxPtr = 100;  // 通过返回的指针间接访问作为左值修改值
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

findMax 函数中,返回了指向数组中最大值的指针。在 main 函数中,通过对返回的指针 maxPtr 进行间接访问,*maxPtr 是左值,可以修改数组中最大值的位置的值。

总结指针间接访问与左值关系要点

  1. 指针间接访问是获取左值的一种方式 通过对指针进行解引用操作(* 运算符),可以得到一个代表内存位置的左值,从而可以对该内存位置进行赋值等操作。

  2. 左值是指针间接访问的基础特性 指针间接访问依赖于左值的概念,因为只有代表可修改内存位置的左值才能通过指针间接访问进行操作。

  3. 注意指针的合法性 在使用指针间接访问作为左值时,要确保指针是合法的,即不是空指针、未初始化指针,也没有发生指针越界等情况,否则会导致未定义行为。

  4. 在函数参数传递和返回值中的应用 指针间接访问与左值的关系在函数参数传递和返回值中有着重要的应用,可以实现对函数外部变量的修改以及获取可操作的内存位置。

深入理解 C 语言中指针间接访问与左值的关系,对于编写高效、正确的 C 程序至关重要,无论是在处理基本数据类型、数组,还是在函数调用和复杂数据结构的操作中,都需要时刻考虑这种关系所带来的影响。