1.字符串跟数值、布尔等一样,都属于“值类型”,MDN上解释如下:
除对象类型(object)以外的其它任何类型定义的不可变的值(值本身无法被改变)。
例如(与 C 语言不同),JavaScript 中字符串是不可变的
(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。
我们称这些类型的值为“原始值”。
2. 简单介绍下程序中使用到的数据,可存放的内存位置。
stack栈段:程序在载入内存后,操作系统给进程分配一段连续的空间,主要用来实现函数调用,在函数执行过程中临时存储运行时需要的数据。此段空间较小,一般为几M。对于函数运行中这些临时数据的存取,有专门的寄存器sp栈顶指针寄存跟bp函数栈帧基址寄存器来进行定位,函数执行时候,将变量依次入栈,函数执行结束时候依次出栈,所以栈中的变量值在函数执行完毕,依次出栈后就不再使用。要想这些变量进行定位,我们首先要知道变量是在函数所有变量中的位置,比如是第一个声明的还是第二个声明的,这个通过我们在代码中声明的位置即可确定。其次我们要知道声明的每个变量占用多大的空间,这个我们需要通过变量的类型来确定。因此临时存储在栈中的变量有这两个特点:1是不能太大,因为空间有限。2是大小确定,因为要使用bp指针来进行定位。
heap堆(数据段):程序运行中,不能满足栈中数据的特点的那些临时数据。比如数据比较大,栈空间总共才几M,放到栈段不合适。或者数据大小不确定,比如我们通过http接口拿到的数据,谁知道多大呢,接口可能返回5条数据,可能返回10条,或者一条也没查到,这类数据必然要放到堆里,可动态扩展,不够了再多申请点。
代码段:此段除了可以放代码编译完的程序指令,还可以放全局常量。因为全局常量跟代码都有相同的特点只能读不能改。
data(数据段):可放全局变量,或者static修饰的变量。相比放在代码段的全局常量,可以进行数据修改。
下面是我在x86Mac笔记本下的c语言通过gcc编译为汇编的代码,可以参考下变量存储位置。
c源文件:
#include <stdio.h>
#include <stdlib.h>
int global_a; // 未初始化的全局变量
int global_b = 2222; // 初始化的全局变量
const char * NAME = "zhao";// 全局常量
int main(void) {
int a = 3333; // int变量 大小:四个字节 存储位置:栈
char * addr = "beijing"; // 指针变量,代码段:存储字符串beijing
// 栈段: 存储字符串beijing的内存地址
return 0;
}
编译指令:gcc -S -masm=intel test2.c -fno-asynchronous-unwind-tables
编译后的汇编文件:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 11, 3
.intel_syntax noprefix
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
## %bb.0:
push rbp
mov rbp, rsp
xor eax, eax
mov dword ptr [rbp - 4], 0
mov dword ptr [rbp - 8], 3333 ## 这是int变量a大小四个字节,
## 栈段所处位置相比上一个数据-4
lea rcx, [rip + L_.str.1] ## 这是获取L_.str.1的内存地址,
## L_.str.1在下面就是字符串beijing
mov qword ptr [rbp - 16], rcx ## 将获取到的L_.str.1的内存地址,
## 放到栈段char * addr,大小8字节,栈段所处位置相比上一个数据-8
pop rbp
ret
## -- End function
.section __DATA,__data
.globl _global_b ## @初始化的全局变量global_b,值是2
.p2align 2
_global_b:
.long 2222 ## 0x8ae
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str 字符串zhao
.asciz "zhao"
.section __DATA,__data
.globl _NAME ## @ 全局常量NAME
.p2align 3
_NAME:
.quad L_.str
.section __TEXT,__cstring,cstring_literals
L_.str.1: ## @.str.1 字符串beijing
.asciz "beijing"
.comm _global_a,4,2 ## @global_a 未初始化的全局变量
.subsections_via_symbols
从编译后的汇编代码就可以很清楚的看到
a. 函数中的局部变量,都是通过bp函数栈帧基址寄存器来进行定位的。rbp-8, rbp-16 等。
b.字符串字面量跟常量,数据都被放到section: __TEXT,后面链接时候会被映射到代码段
c.指针类型变量,在栈中存储数据所在的内存地址,如果是64位机器,指针大小就是8个字节。
3.js中使用到的字符串。
通过文件读取或者接口返回的应该在heap里。
对于字面量声明的比如 const str1 = 'AAAAAA'这种的,我觉得应该放在代码段。
通过new String()生成的对象肯定是在js引擎的heap里,可是作为参数的字符串字面量呢?我觉得应该也是放在代码段。
但是一直没找到相对权威的介绍,在chrom的devtools的文档中介绍memory面板的那部分,有一段字符串存储的介绍,地址:chrome-devtools-doc 。 截图如下。说是string可能存储在虚拟机vm heap里,或者在渲染进程的内存里,这部分数据呢,不受js虚拟机的垃圾回收器管理。这段介绍上面就是数字numbers的存储,可以看到大的数字也是存在heap里的。
4.下面是我通过chrom-devtools调试工具的memory面板,来进行vm heap采样时用到的一些过程,可简单参照下。
我的demo代码如下,比较简单,定义几个字符串,一种是普通字面量定义,一种是通过"+"连接字符串,一种是new一个String对象。
<script>
function test (){
const str1 = 'AAAAAA';
const str2 = 'AAAAAA-BBBBBB';
const str3 = str1 + str2;
const str4 = new String('AAAAAA-CCCCCCC');
}
test(); </script>
5.然后打开Devtools的memory面板,在函数执行完前断点,点击“take heap snapshot”,得到内存快照如下,可以看到存放到heap内存中的各类对象,对象是以Class的constructer分组的。
5.Class很多,我们可以在Class filter那个输入框里输入 string 来过滤下,字符串相关的。经过滤后如下图,明显是有字符串的。
6.我们展开(string) 这个class下的,然后按照名称排一下序,很容易就找到了代码中声明的AAAAAA,AAAAAA-BBBBBB而且还有new String()中传的参数AAAAAA-CCCCCCC。
所以通过字面量声明字符串,即使作为函数参数的默认值,放在(string)这个类下。
7.猜也猜到了,通过“+”字符生成的连接字符串在(concatenated string)类下
8.同理通过new String()生成的String对象在String类下。
总结:
1.通过字面量声明的字符串,虽然在heap内存中,可是一直没有被gc回收,即使指向他的函数变量已经出栈了。可以通过让函数执行完毕,然后点击memory面板下的“Collect garbage” 按钮,然后再进行内存快照来验证一下。
2.js中的不同数据类型存储位置究竟是栈还是堆,最终还是要以js解释器v8引擎的实现为准。如果v8能提供一个工具,可以查看js编译为汇编代码后的样子就好了。