C- 一个程序引发的问题

C程序如下:

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

struct student_t
{
    
    
    char *id;
    char *name;
    char *score;
};

typedef struct student_t *Student_t;

void print_stu_info(Student_t stu)
{
    
    
    printf("%s  %s  %s\n", stu->id, stu->name, stu->score);
}

int main()
{
    
    
    Student_t stu = malloc(sizeof(*stu));
    stu->id = "001";
    stu->name = "xiaonan";
    stu->score = "95";
    print_stu_info(stu);

    *((char **)((char *)stu + 2*sizeof(char *))) = "100";
    print_stu_info(stu);

    return 0;
}

程序运行结果如下:

majn@tiger:~/C_Project/dl_project$ ./hack_struct 
001  xiaonan  95
001  xiaonan  100

问题一


Student_t stu = malloc(sizeof(*stu));

第一次见这种写法,真的没有问题吗?写成

Student_t stu = malloc(sizeof(*Student_t)); 

为什么编译报错?


在这行代码中出现了错误:

Student_t stu = malloc(sizeof(*Student_t));

问题在于 *Student_t。在这里,Student_t 是一个类型,而不是一个变量。不能对类型进行解引用。

正确的方式是,使用 sizeof(struct student_t)sizeof(*stu) 来获取结构体的大小。如果我们要分配一个结构体的大小并返回一个指向该结构体的指针,应该这样写:

Student_t stu = malloc(sizeof(struct student_t));

或者:

Student_t stu = malloc(sizeof(*stu));

将上述更正后的代码行替换原有代码,应该能够成功编译。


关于第一种写法的问题,这实际上与C语言的声明和初始化语法有关。

在C语言中,当我们写:

Student_t stu = malloc(sizeof(*stu));

以下是发生的事情的简化描述:

  1. 编译器首先处理 Student_t stu 部分。这里,它知道声明了一个名为 stuStudent_t 类型的指针变量。从这一点开始,stu 已经被声明并可以在其后的代码中使用。

  2. 接下来,编译器处理 = malloc(sizeof(*stu)) 部分。这时,由于 stu 已经被声明,所以可以在 sizeof(*stu) 中使用它。

因此,尽管 = 是右结合的,但在整个声明和初始化过程中,声明总是先于初始化发生。

如果将这两个步骤分开,那么确实需要确保在使用一个变量之前已经声明了它。但在同一语句中进行声明和初始化时,该语句的声明部分会首先被处理。


问题二

如何通过直接操作内存地址的方式来修改 stu->score 的值?

如果想通过直接操作内存地址来修改 stu->score 的值,首先,需要确保准确地计算了偏移量,并且将正确的值赋给正确的地址。

struct student_t 中,字段按以下顺序出现:

  1. char *id
  2. char *name
  3. char *score

所以,stu 指针首先指向 id,然后是 name,最后是 score

我们可以通过增加 stu 的地址来修改 score,但需要知道系统的指针大小(通常是 4 字节(32位系统)或 8 字节(64位系统))。

以下是可能的写法(假设在 64 位系统上):

*((char **)((char *)stu + 2*sizeof(char *))) = "100";

解释:

  • 首先,我们将 stu 转换为 char * 类型,使我们能够按字节递增其地址。
  • 接下来,我们增加了 2 * sizeof(char *) 字节,这样我们就可以跳过前两个 char * 字段,指向 score
  • 然后我们将结果转换回 char ** 类型,以便我们可以将字符串的地址("100")赋给它。

下面,我们来详细地探讨这行代码的每一个部分:

首先,来看这个代码的目的:它旨在直接更改 stu->score 的指针值,使其指向新的字符串字面量 "100"

现在,我们一步一步来分析这行代码:

  1. (char *)stu:
    这将 stu 指针从 Student_t 类型转换为一个字符指针 (char *)。这样做允许我们以字节为单位进行指针算术。

  2. 2*sizeof(char *):
    这里我们计算两个指针的大小。每个 char * 指针的大小通常为 4 字节(32位系统)或 8 字节(64位系统)。因此,这部分代码计算 stu 结构中前两个 char * 指针(idname)所占用的总字节。

  3. (char *)stu + 2*sizeof(char *):
    这将上述两个部分结合起来,即将 stu 的起始地址增加了前两个 char * 指针所占用的字节,从而将地址定位到 score 字段。

  4. (char **)((char *)stu + 2*sizeof(char *)):
    现在,我们再次进行类型转换,将计算出的 score 地址从 char * 转换为 char **。因为我们最终的目标是更改一个指针的值(指向一个新的字符串),而不仅仅是更改一个字符。

  5. ((char **)((char *)stu + 2*sizeof(char *))) = “100”:
    最后,我们使用解引用运算符 * 来访问和更改 score 指针的值,使其指向新的字符串 "100"

直接操作内存地址是非常危险的,并且容易引入错误。这种做法破坏了抽象,使代码变得难以阅读和维护。因此,在实际编程中,应尽量避免使用这样的方式。

猜你喜欢

转载自blog.csdn.net/weixin_43844521/article/details/133419591
C-