野指针与结构体成员指针,动态分配存储空间

1、野指针举例

例1:明显的野指针,直接赋值

#include <stdio.h>
int main()
{
    int a;
    int *p;
    *p=10;//错误来源与此处
    printf("%d %d\n",a,*p);
    return 0;
}

*p没有被初始化,而直接赋值为10,相当于把某一内存中直接存入“10”;

未初始化的*p指向一个随机的内存单元0x2,该内存单元(0x2)中的值(未知)由计算机内存管理的规则决定,不能随意被程序更改。

指针变量*p初始化的意义:定义*p的指向!而不是初始化前的随机指向!!!

例1修改后如下,可以正常运行:

#include <stdio.h>
int main()
{
    int a=2;
    int *p=&a;//必须初始化
    *p=10;
    printf("%d %d\n",a,*p);
    return 0;
}

例2:未分配内存的野指针

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

struct test
{
    int x;
    char *str;
};

int main()
{
    struct test a;
    a.x=100;
    char s[]="hello";
    strcpy(a.str,s);//复制字符串s到结构体变量a的数据项str
    printf("%d %s\n",a.x,a.str);
    return 0;
}

其中,strcpy(a.str,s)的作用是把数组s[ ]的首地址赋值给a.str

但是a.str在未初始化前,指向的是一个内存中的随机地址,不能随便被更改;

未分配a.str的具体指向时(未初始化),a.str就是一种野指针!

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

struct test
{
    int x;
    char *str;
};

int main()
{
    struct test a;
    a.x=100;
    char s[]="hello";
    a.str=(char*)malloc(strlen(s)+1);//!!!通过malloc给a.str分配了一端长度为(s+1)的内存空间
    strcpy(a.str,s);//复制字符串s到结构体变量a的数据项str
    printf("%d %s\n",a.x,a.str);
    return 0;
}

malloc 函数其实就是在内存中:找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址, 这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以不连续。我们作为程序员,关注的 是逻辑上的连续,其它的,操作系统会帮着我们处理的。

分配空间口的a.str就不再是随意指向的野指针了,就可以用strcpy进行赋值了,程序就不会报错。

 

例3:不易被发现的野指针

struct test
{
    int x;
    char *str;
};

int main()
{
    struct test a;
    a.x=100;
    char s[]="hello";
    a.str=s;//指针的赋值,数组首元素的地址赋值给a.str
    printf("%d %s\n",a.x,a.str);
    return 0;
}

该程序编译完成没有bug,并且正常运行!

但是,详细分析:当s[ ]不是一个固定的数组,而是由其它机制带来的数组,例如该数组在被a.str使用时已经被释放掉了;

则a.str指向的是一个程序不具备访问权限的内存单元,a.str就会变成“野指针”!!!如下程序所示:

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

struct test
{
    int x;
    char *str;
};

int main()
{
    struct test a;
    a.x=100;
    char *s=(char*)malloc(10);//第一步,给s分配长度为10的内存地址
    strcpy(s,"hello");//第二步,在该地址中存放hell\0
    a.str=s;//第三步,将s的地址赋值给a.str
    free(s);//第四步,释放掉s的内存空间
    printf("%d %s\n",a.x,a.str);//第五步,a.str输出的值是已经被释放掉的内存中的随机值
    return 0;
}

程序运行结果:

a.str的输出结果是:?n

实际上对该程序而言,每一次运行输出的a.str的值都不同,因为a.str通过s赋值,但是s的内存又被释放掉了,因此a.str指向的值为内存的随机内容。

如果注释掉free(s),可以尝试一下。

备注:一定要动态分配存储空间!否则一定会有机会错!

 

 

<think>我们正在讨论结构体指针结构体变量在内存中的存储方式差异。根据引用[3]和引用[5],我们可以总结如下:1.**结构体变量**:-结构体变量是直接存储整个结构体数据的实体。当定义一个结构体变量(如`studentxiaoming;`)时,编译器会为整个结构体分配连续的内存空间,大小等于结构体所有成员的大小之和(可能包含内存对齐的填充)。-例如,在引用[5]的代码中,`xiaoming`这个结构体变量被定义后,其内存已经分配在栈上(如果是局部变量)或全局数据区(如果是全局变量),可以直接访问其成员(如`xiaoming.id`,`xiaoming.name`等)。2.**结构体指针**:-结构体指针是一个指针变量,它存储的是结构体内存块的地址。定义结构体指针(如`stuxiaohong;`,其中`stu`是指向`student`结构体指针类型)时,指针变量本身需要内存(通常4或8字节,取决于系统),但此时它还没有指向有效的结构体内存。-必须为指针分配内存(例如通过`new`或`malloc`)后,才能使用。如引用[5]中`xiaohong=newstudent;`,这会在堆上分配一个`student`结构体大小的内存块,并将该内存块的起始地址赋给`xiaohong`。-访问结构体指针成员需要使用箭头运算符(`->`),如`xiaohong->id`。**内存存储方式差异**:-**结构体变量**:内存直接包含结构体的所有成员数据,存储在栈或全局/静态存储区。访问成员时,通过变量名加`.`运算符直接访问内存位置。-**结构体指针**:指针变量存储一个地址(指向堆或栈上已分配的结构体内存块)。指针变量本身的内存较小(一个指针大小),而实际的结构体数据存储在指针所指向的内存区域。访问成员时,先通过指针找到结构体地址,再通过偏移量访问成员(使用`->`运算符)。**内存布局示例**:假设有结构体:```cstructstudent{charid[12];//12字节charname[8];//8字节intage;//4字节(假设对齐后结构体大小为24字节)};```-**结构体变量`xiaoming`**:-在栈上分配24字节连续内存,存储`id`、`name`和`age`的值。-**结构体指针`xiaohong`**:-指针变量`xiaohong`本身在栈上(4或8字节),存储一个地址(例如0x1000)。-通过`newstudent`在堆上分配24字节内存(地址0x1000),存储实际数据。-访问`xiaohong->id`时,先取出指针值0x1000,然后访问0x1000开始的12字节。**总结区别**:|特性|结构体变量|结构体指针||--------------|------------------------------|--------------------------------||**内存分配**|定义时即分配完整结构体内存|定义时只分配指针内存,需额外分配结构体内存||**存储位置**|栈/全局数据区|指针在栈,指向的数据通常在堆(也可指向栈)||**访问方式**|直接通过`.`运算符|通过`->`运算符间接访问||**内存占用**|整个结构体大小|指针大小+结构体大小(动态分配)||**生命周期**|自动(栈)或程序运行期(全局)|指针生命周期同变量,指向的内存需手动释放(堆)|注意:结构体指针也可以指向栈上的结构体变量(如`students;student*p=&s;`),此时不需要动态分配,但指针仍然只存储地址,访问方式为`p->member`。引用[3]强调,结构体指针必须指向有效的内存(通过分配或指向已有变量),否则是指针。引用[5]通过代码示例展示了两种变量的定义和初始化方法。§§相关问题§§1.结构体变量和结构体指针在函数参数传递时有何区别?2.如何选择使用结构体变量还是结构体指针?3.结构体指针初始化或未分配内存直接访问会导致什么问题?</think>### 结构体指针结构体变量内存存储方式的区别 #### 1. **结构体变量的内存存储** - **直接存储数据**:结构体变量在定义时即分配完整的连续内存空间,大小等于所有成员变量大小之和(含内存对齐填充)。 - **存储位置**: - 局部变量:栈内存(函数结束时自动释放) - 全局变量:全局/静态数据区(程序生命周期内存在) - **访问方式**:通过`.`运算符直接访问成员(如`var.member`) - **内存示意图**: ``` | 成员1 | 成员2 | ... | 成员n | ← 连续内存块 ``` - **代码示例**(引用[5]): ```c student xiaoming; // 定义即分配内存 strcpy(xiaoming.id, "20180000001"); // 直接操作内存 ``` #### 2. **结构体指针的内存存储** - **间接存储数据**: - 指针变量本身存储一个地址(4/8字节),指向结构体的实际内存位置。 - **必须额外分配内存**:指针需指向有效的结构体内存(通过`new`/`malloc`或已有变量地址)。 - **存储位置**: - 指针变量:栈内存(大小固定,仅存储地址) - 实际数据:堆内存(手动分配/释放)或栈内存(指向其他变量) - **访问方式**:通过`->`运算符间接访问(如`ptr->member`) - **内存示意图**: ``` 指针变量: [地址值] → 指向 → | 成员1 | 成员2 | ... | 成员n | ``` - **代码示例**(引用[5]): ```c stu xiaohong; // 仅分配指针变量(未初始化) xiaohong = new student; // 动态分配堆内存 strcpy(xiaohong->id, "20180000002"); // 间接访问 ``` #### 3. **核心区别总结** | 特性 | 结构体变量 | 结构体指针 | |--------------|------------------------------|--------------------------------| | **内存分配** | 定义时自动分配完整结构体内存 | 仅分配指针变量内存,需额外分配数据内存 | | **存储内容** | 直接存储结构体数据 | 存储指向结构体数据的地址 | | **访问方式** | 直接访问(`.`运算符) | 间接访问(`->`运算符) | | **内存位置** | 栈/全局数据区 | 指针在栈,数据在堆或栈 | | **生命周期** | 作用域结束自动释放(局部变量) | 需手动释放堆内存(`delete/free`)| | **灵活性** | 固定内存位置 | 可指向不同内存块,支持动态结构 | #### 4. **关键注意事项** - **指针风险**:未初始化结构体指针直接访问会导致未定义行为(引用[3])。 - **内存开销**:指针变量本身占用小内存(地址大小),但需额外开销管理指向的内存块。 - **效率差异**:结构体变量访问更快(直接内存操作),指针需解引用(间接访问)。 - **传递效率**:函数参数传递时,传递指针(4/8字节)比传递大型结构体变量(复制整个结构体)更高效(引用[4])。 > 示例分析(引用[5]): > `xiaoming`(结构体变量)在栈上分配完整内存,而`xiaohong`(指针)需通过`new student`在堆上分配内存后,才能存储实际数据。访问`xiaoming.id`直接操作栈内存,`xiaohong->id`需先解引用指针找到堆内存地址再访问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值