scanf_s 遇到的问题

好久没使用scanf,今天使用时提示用scanf_s,以为是vc自己家的东西,后搜索下原来c11已经也有了,只不过只有微软现在支持,”C11中加入了strcat_s、strcpy_s等,但现在只有微软支持”,上句话别人说的,自己没具体测试过其它平台 然后,自己用F12查看scanf_s原型,一脸懵,虽然看过stl源码的一些书,但只看现在的原型,还是看不懂,再搜索,看有没有讲解此内容的,没结果,明天或以后解决 然后,又看到搜索 “scanf_s 向字符数组输入内容” 的网页:https://blog.csdn.net/hwithk/article/details/47029771; 看了看,博主很厉害的样子,把内存调试使用的很熟悉;内存调试 不懂 继续搜索 “什么时候需要 内存调试” 找到:https://www.cnblogs.com/lancidie/archive/2013/05/04/3059854.html 又遇见一个词:内存编程,(搜索) 这次收货很大,遇见一个很好的博主,以下是他的原文 ———- 内存编程(所有的情况都有例子)(转:https://www.cnblogs.com/findumars/p/5929820.html)

1. 内存分配方式

1.1 内存分配的几种方式

(1) 从静态存储区域分配。

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。

(2) 在栈上创建。

在执行函数时,函数的参数值,内局部变量的存储单元都可以在栈上创建。函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3) 从堆上分配,亦称动态内存分配。

程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

例子程序

//main.cpp

int a = 0; //静态存储区(初始化区域)

char *p1; //静态存储区(未初始化区域)

void example()

{

int b;      //栈

char s[] = “abc”;   //栈

char *p2;      //栈

static int c =0;     //静态存储区(初始化区域)

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);    //分配得来的10和20字节的区域就在堆上

}

另外,在嵌入式系统中有ROM和RAM两类内存,程序被固化进ROM,变量和堆栈设在RAM中,用const定义的常量也会被放入ROM。

注:用const定义常量可以节省空间,避免不必要的内存分配。

例如:

#define PI 3.14159                  //常量宏

const double g_pi = 3.14159; //此时并未将Pi放入ROM中

……

double a = g_pi;                    //此时为Pi分配内存,以后不再分配!

double b =PI;                           //编译期间进行宏替换,分配内存

double c = g_pi;                       //没有内存分配

double d = PI;                          //再进行宏替换,又一次分配内存!

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

1.2 几种分配方式的内存生命期

(1)   静态分配的区域的生命期是整个软件运行期,就是说从软件运行开始到软件终止退出。只有软件终止运行后,这块内存才会被系统回收。

(2)   在栈中分配的空间的生命期与这个变量所在的函数、类和Block(即由{}括起来的部分)相关。如果是函数中定义的局部变量,那么它的生命期就是函数被调用时,如果函数运行结束,那么这块内存就会被回收。如果是类中的成员变量,则它的生命期与类实例的生命期相同。如果在Block中定义的局部变量,则它的生命期仅在Block内。

(3)   在堆上分配的内存,生命期是从调用new或者malloc开始,到调用delete或者free结束。如果不调用delete或者free,则这块空间只有到软件运行结束后才会被Windows系统回收。

2. 常见的内存错误及其对策

(1)   内存分配未成功,却使用了它。

犯下这种错误主要原因是没有意识到内存分配会不成功。

常用解决办法是,在使用内存之前检查指针是否为NULL。

如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。

如果是用new或者malloc来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。若指针为NULL,则应立即返回相应的错误码,说明内存不足而中止调用。

int Func(void)

{

char *p = (char *) malloc(100);

if(p == NULL)

{

              return ERR_NO_MEMORY;

       }

}

(2)   内存分配虽然成功,但是尚未初始化就引用它。

犯下这种错误主要原因有两个:

一是没有初始化的观念;

二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。

内存的缺省初值究竟是什么并没有统一的标准。但是对于全局变量和静态变量如果没有手工初始化,编译器会将其初始化为零,而对栈内存和堆内存则不作任何处理。

另外,VC在Debug和Release状态下在初始化变量时所做的操作是不同的。Debug是将每个字节位都赋值成0xcc,以有利于调试。而Release的赋值是直接从内存中分配的,内容近似于随机。所以如果在没有初始化变量的情况下去使用它的值,就会导致问题发生。

无论用何种方式创建数组,都不要忘记赋初值,可以使用memset为数组赋零值。

#define AVP_STREAM_RCV_BUFFER_NUM      (5)

#define AVP_STREAM_SND_BUFFER_NUM             (5)

……

AVP_StreamRcvBuffer_t g_AVP_StreamRcvBufferList[AVP_STREAM_RCV_BUFFER_NUM];

AVP_StreamSndBuffer_t g_AVP_StreamSndBufferList[AVP_STREAM_SND_BUFFER_NUM];

……

memset( g_AVP_StreamRcvBufferList, 0, sizeof( g_AVP_StreamRcvBufferList ) );

memset( g_AVP_StreamSndBufferList, 0, sizeof( g_AVP_StreamSndBufferList ) );

……

此外,任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

例如

char *p = NULL;

       int *i = (int *) malloc(100);

(3)   内存分配成功并且已经初始化,但操作越过了内存的边界,导致缓冲区溢出。

例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

越界?越谁的界?当然是内存。一个变量存放在内存里,想读的是这个变量,结果却读过头了,很可能读到了另一个变量的头上。这就造成了越界。

访问越界会出现什么结果?

首先,它并不会造成编译错误! 就是说,C/C++的编译器并不判断和指出代码“访问越界”了。此外,数组访问越界在运行时,它的表现是不定的,有时似乎什么事也没有,程序一直运行(当然,某些错误结果已造成);有时,则是程序一下子崩溃。

请看下面的例子:

让用户输入学生编号,查询学生的考试成绩。如果代码是这样:

int mark[100]; 

//让用户输入学生编号,设现实中学生编号由1开始:

cout << “请输入学生编号(在1~100之间):”

int i;

cin >> i;

//输出对应学生的考试成绩:

cout << info[i-1];

 

这段代码看上去没有什么逻辑错误。可是,某些用户会造成它出错。如果用户不输入1到100之间的数字,而是输入105,甚至是-1。这样程序就会去尝试输出:mark[104] 或 mark[-2],导致数组操作越界。

对于这类问题的解决办法就是,我们需要在输出时,做一个判断,发现用户输入了不在编号范围之内的数,则不输出或者提示用户重新输入合法值。这样就会避免错误出现。

以上是数组读操作的越界,同样地,在对一块缓冲区进行写操作时,如果向缓冲区内填充的数据位数超过了缓冲区本身的容量,便会发生缓冲区溢出。

当一个超长的数据进入到缓冲区时,超出部分就会被写入其他缓冲区,其他缓冲区存放的可能是数据、下一条指令的指针,或者是其他程序的输出内容,这些内容都被覆盖或者破坏掉。可见一小部分数据或者一套指令的溢出就可能导致一个程序或者操作系统崩溃。

请看下面的代码:

void DoSomething (char *cBuffSrc, DWORD dwBuffSrcLen) 
{
  char cBuffDest[32] ;
  memcpy (cBuffDest, cBuffSrc, dwBuffSrcLen) ;
}

上面的函数在参数dwBuffSrcLen的实际值小于等于cBuffDest的长度时不会出现问题,但是如果dwBuffSrcLen的值大于cBuffDest的长度,当memcpy 将数据复制到 cBuffDest 中时,来自 DoSomething 的返回地址就会被更改,因为 cBuffDest 在函数的堆栈框架上与返回地址相邻。

如果将函数进行适当的修改,使 memcpy 的调用具有防御性,它将不会复制多于目标缓冲区存放能力的数据了。

void DoSomething (char *cBuffSrc, DWORD dwBuffSrcLen) 

  const DWORD dwBuffDestLen = 32 ;
  char cBuffDest[dwBuffDestLen] ;
  memcpy (cBuffDest, cBuffSrc, min(dwBuffDestLen, dwBuffSrcLen)) ;
}

(4)   分支处理不完整,错误处理不当,导致忘记释放内存,造成内存泄露。

我们常说的内存泄漏一般是指堆内存的泄漏。应用程序一般使用malloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

以下这段小程序演示了堆内存发生泄漏的情形:

void MyFunction(int nSize)

{

char* p= new char[nSize];

if( !GetStringFrom( p, nSize ) ){

MessageBox(“Error”);

return;

}
…//using the string pointed by p;
delete p;

}

当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是C函数可以在任何地方退出,所以一旦对分支处理不完整或者错误处理不当的话,就会发生内存泄漏。虽然函数体内的局部变量在函数结束时自动消亡,但是局部的指针变量所指向的内存并不会被自动释放。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,可能看不到错误,但终有一次程序突然死掉,系统出现提示:内存耗尽。

动态内存的申请与释放必须配对,如果程序在入口处动态申请了内存,那么在程序的每个出口处都必须释放该内存空间。

(5)   释放了内存却继续使用它。

有三种情况:

(a)   程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

(b)   函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

(c)   使用free释放了内存后,没有将指针设置为NULL。导致产生“野指针”,即不是NULL指针,而是指向“垃圾”内存的指针。“野指针”是很危险的,因为使用if语句进行判断对它不起作用。

char *p = (char *) malloc(100);

strcpy(p, “hello”);

free(p);       // p所指的内存被释放,但是p所指的地址仍然不变

if(p != NULL)     // 没有起到防错作用

{

       strcpy(p, “world”);     // 出错

}

同样地,指针操作超越了变量的作用范围也造成“野指针”,示例程序如下:

class A

{    

public:

       void Func(void){ cout << “Func of class A” << endl; }

};

void Test(void)

{

       A *p;

       {

              A a;

              p = &a; // a 的生命期仅在Block内

}

       p->Func();           // p是“野指针”

}

函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。由于a所占据的内存并没有被覆盖,所以暂时不会出现问题。但是当堆栈发生变化后,如调用函数或者定义了新的局部变量,则将发生内存错误。

3. 指针与数组的对比

C/C++程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

下面以字符串为例比较指针与数组的特性。

3.1 修改内容

//数组

char a[] = “hello”;

a[0] = ‘X’;

// 指针

char *p = “world”;     // 注意p指向常量字符串

p[0] = ‘X’;               // 编译器不能发现该错误

字符数组a的容量是6个字符,其内容为hello”0。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world”0),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

3.2 内容复制与比较

不能对数组名进行直接复制与比较。

// 数组…

char a[] = “hello”;

char b[10];

strcpy(b, a);                // 不能用b = a;

if(strcmp(b, a) == 0)   // 不能用if (b == a)

// 指针…

int len = strlen(a);

char *p = (char *)malloc(sizeof(char)*(len+1));

strcpy(p,a);                 // 不要用 p = a;

if(strcmp(p, a) == 0)   // 不要用 if (p == a)

若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

3.3 计算内存容量

用运算符sizeof可以计算出数组的容量(字节数)。

char a[] = “hello world”;

char *p = a;

sizeof(a) = ? (12字节, 注意别忘了’”0’)

sizeof(p) = ? (4字节)

sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。

注意:当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

void Func(char a[100])

{

              …  

}

sizeof(a) = ? (4字节而不是100字节)

不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。

4. 实例解析

² 不要用函数的指针参数去申请动态内存

void GetMemory(char *p, int num)

{

       p = (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

       char *str = NULL;

       GetMemory(str, 100);       // str 仍然为 NULL

       strcpy(str, “hello”);            // 运行错误

}

解析:Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL。问题出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”。

void GetMemory2(char **p, int num)

{

       *p = (char *)malloc(sizeof(char) * num);

}

void Test2(void)

{

       char *str = NULL;

       GetMemory2(&str, 100); // 注意参数是 &str,而不是str

       strcpy(str, “hello”);

printf(%s, str);    

       free(str);      

}

² 不要返回临时变量的指针和引用

void loop();

void addr();

int main ()

{

addr();

loop();

}

long *p ;

void  loop()

{

long i, j ;

j = 0;

for ( i = 0 ; i < 10 ; i++ ){

(*p)–;

j++;

}

}

 void   addr()

{

long k;

k = 0;

p = &k;

}

解析:这里的问题出现在保存临时变量的地址上。由于addr函数中的变量k在函数返回后就已经不存在了,但是在全局变量p中却保存了它的地址。在下一个函数loop中,试图通过全局指针p访问一个不存在的变量,而这个指针实际指向的却是另一个临时变量i,这就导致了死循环的发生。

看一下这个程序中局部变量的地址分配。addr()中的局部变量k,loop()中的局部变量i、j,它们的地址分配可以如下图所示:

j

k/i

 

p——à

可以理解为i和k占用同一个内存单元(因为他们都是局部变量,不可能同时出现在执行语句中而导致冲突)。在addr()函数中,系统为变量k安排了地址,并将指针p指向k所在的单元,当从addr()函数返回的时候,系统收回了分配给k的地址(这些实际上就是在栈里进行的)。在进入loop()函数以后,就一次为局部变量i,j分配地址,因为i的类型和k相同,所以它占用的空间大小和k相同,系统按序分配地址,很显然分配给i的地址就是在addr()中分配给k的地址。因为指针p是一个全局变量,它的值(此时即i所在的单元地址)未变,所以现在p所指的是现在的i所在的地址,故(*p)–实际上成了i–,所以i一直在-1和0之间变化,程序陷入死循环。

² 数组访问越界

int main ()

{

int   i;

int   a[10];

for(i=0; i<=10; ++i)

a[i] = 0;

return 0;

}

解析:在main中,i和数组a是采用静态存储分配策略的。它们所占的空间大小在编译时是确定的。但是从高地址开始分配空间的。如下所示:

a[0]

a[1]

a[2]

a[3]

a[4]

a[5]

a[6]

a[7]

a[8]

a[9]

i

低地址

高地址

数组实际上就是一块内存,a就是数组的首地址,[]中的值是偏移值,所以a[10]实际上就是i,a[i] = 0就是i=0,导致死循环。

int    i;

int    a[10] ;

只要将上面两句交换一下位置,在这个程序中就不会死循环了。

当然,访问a[10]本来就是一个错误,数组越界,后果不堪设想,由于C对数组没有越界检查,所以编译没问题。

² 将一个数组赋值为等差数列,并将会在函数的外部使用它

int *GetArray( int n )

{

int *p = new int[n];

for ( int i = 0; i < n; i++ )

{ p[i] = i; }

return p;

}

解析:检查内存泄露的最好办法,就是检查完全配对的申请和释放,在函数中申请而在外部释放,将导致代码的一致性变差,难以维护。而且,一个人写的函数不一定是他自己使用的,这样的函数别人会不知道该怎么适当的使用。因此最好的解决办法就是在函数调用的外面将内存申请好,函数只对数据进行复制。

void GetArray( int *p, int n )

{

for ( int i = 0; i < n; i++ )

{ p[i] = i; }

} 

² 写一个类封装对指针的申请内存、释放和其它一些基本操作

class A

{

public:

A( void ) {}

~A( void ) { delete []m_pPtr; }

void Create( int n ){ m_pPtr = new int[n]; }

private:

int *m_pPtr; 
};

解析:不合理的代码就在于当重复调用Create的时候就会造成内存泄露,解决的办法就是在new之前判断一下指针是否为0。要能够有效的执行这个判断,则必须在构造的时候对指针进行初始化,并为这个类添加一个Clear函数来释放内存。

如果是C程序,可以使用自己的函数来封装malloc和free,虽然这样不能避免内存泄漏,但是至少可以使内存泄漏变得容易检查。

class A

{

public:

A( void ) : m_pPtr(0){}

~A( void ) { Clear(); }

bool Create( int n ){

if ( m_pPtr ) return false;

m_pPtr = new int[n];

return ture;

}

void Clear( void ) { delete []m_pPtr; m_pPtr = 0; }

private:

int *m_pPtr;

};

5. 小结

内存编程的几点规则:

规则1-用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

规则2-不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

规则3-避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

规则4-动态内存的申请与释放必须配对,防止内存泄漏。

规则5-用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

 

http://www.cnblogs.com/qiubole/archive/2008/03/07/1094677.html

———- 面向对象编程(三)——程序执行过程中内存分析(转:https://www.cnblogs.com/GarfieldEr007/p/7198009.html)

内存分析(SxtStu.java)

Java程序运行在JVM上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性。所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提。

 一个完整的Java程序运行过程会涉及以下内存区域:

         寄存器: JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

         栈: 保存局部变量的值,包括:a.用来保存基本数据类型的值;b.保存类的 实例 ,即堆区 对象 的引用(指针)。也可以用来保存加载方法时的帧。

         堆: 用来存放动态产生的数据,比如new出来的 对象 。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

         常量池: JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的 符号引用(1) 。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。 常量池存在于堆中 。

         代码段: 用来存放从硬盘上读取的源程序代码。

        全局数据段: 用来存放static定义的静态成员或全局变量。分配该区时内存全部清0,结果变量的初始化为0。

下图表示内存分配图:

▶ 栈(stack)

  存放:局部变量;

▶ 堆(heap)

  存放new出来的对象;

▶ 方法区(method)

  存放:类的信息(代码)、static变量、常量池(字符串常量)等.

栈的特点:自动分配连续的空间,后进先出;一般放置局部变量

堆的特点:不连续的空间;放置创建new出的对象;

下面对如下图进行分析:

具体内存分析图如下所示:


 

现对图2进行分析:

Student类:

复制代码
复制代码
public class Student {

    //静态数据
    String name;
    int id;
    int age;
    int weight;

    Computer computer;//每个学生有台电脑

    //动态的行为
    public void study(){
        System.out.println(name+"在学习");
    }

    public void sayHello(String sname){
        System.out.println(name + "向" +sname +"说,你好~");
    }

}
复制代码
复制代码

Computer类:

public class Computer {

    String brand;//品牌
    int cpuSpeed;//cpu
}

则:

复制代码
复制代码
public class Test2 {
    public static void main(String[] args) {
        Student s1=new Student();
        s1.name="熊二";
        s1.age=18;

        Computer c=new Computer();
        c.brand="联想";
        c.cpuSpeed=100;

        s1.computer =c;

        //c.brand= "戴尔"; 
  System.out.println(s1.computer.brand); } }
复制代码
复制代码

具体内存分析如下:

:如果加上注释部分”c.brand= “戴尔”;”,s1.computer.brand最后输出的就是”戴尔”.因为它们指向的是同一个地方。

对于java 和内存之间的注意事项

 1. 一个Java文件,只要有main入口方法,我们就认为这是一个Java程序,可以单独编译运行。

  2. 无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

3. 分清什么是实例什么是对象。Class a= new Class();此时a叫实例,而不能说a是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

 4 . 栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

 5 . 以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

6  . 类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

 

可以参考的文章:

JVM内存堆布局图解分析

【java】内存分析

Jvm内存模型

 

from: http://www.cnblogs.com/Qian123/p/5166351.html

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
scanfscanf_s都是用于从标准输入中读取数据的函数。两者的功能相同,都可以读取不同类型的数据,如整数、浮点数、字符串等。 不同之处在于scanf_s是Microsoft公司的VS开发工具提供的一个安全版本的scanf函数,它在调用时需要提供一个数字参数,表示最多读取多少位字符。这样可以避免缓冲区溢出的风险。而scanf没有这个参数,它会根据格式字符串的要求一直读取字符,直到遇到空白字符为止。 另外,scanf_s要求在最后一个参数中指定接收缓冲区的大小,即读取的字符个数不超过缓冲区的容量。而scanf没有这个要求,它会根据格式字符串中的数据类型来确定读取的字符个数。 所以,如果你想要更加安全地读取输入数据,建议使用scanf_s。如果你对输入的控制比较严格,并且不担心缓冲区溢出的问题,也可以使用scanf。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [谈谈scanfscanf_s的那些事](https://blog.csdn.net/zengjierong_plus/article/details/89251483)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [scanfscanf_s的区别、解决VS返回值被忽略的报错问题](https://blog.csdn.net/m0_68997646/article/details/130717098)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值