Most of the things we learn are elements that are not related to a particular programming language, but I'm mostly focused on JavaScript, and some C. Let's start with a simple C program that reads a song and a band name and then outputs them to the user:
STACKFOLLY.C Download
#include <stdio.h>
#include <string.h>
char *read ()
{
char data[64];
Fgets (data, stdin);
return data;
}
int main (int argc, char *argv[])
{
char *song, *band;
Puts ("Enter song, then band:");
Song = read ();
Band = read ();
printf ("\n%sby%s", song, band);
return 0;
}
If you run this program, you will get nothing. (= = Shows program output):
./stackfolly
= Enter song, then band: The
past is a grotesque Animal of
Montreal
? ǿontreal
= = by? ǿontreal
(Once the C newbie said) something went wrong.
It turns out that the contents of a function's stack variable are only available during the stack frame activity, that is, only before the function returns. In the above return, the memory used by the stack frame is considered usable and can be overwritten in the next function call.
The following figure shows what exactly happened in this case. This diagram now has a mirror map, so you can click on a piece of data to see the relevant GDB output (GDB command here). As long as read () reads the name of the song, the stack will look like this:
At this time, this song variable immediately points to the name of the song. Unfortunately, the memory location where the string is stored is reused for the stack frame of any function that is called next. In this case, read () is called again and uses the same position of the stack frame, so the result becomes the following image:
The band name is read into the same memory location, and the name of the song stored in front is overwritten. Band and song end up pointing exactly to the same point. Finally, we can't even get the correct output of the "of Montreal" (the name of a European and American band). Can you guess why.
Therefore, even if the stack is useful, there are important limitations. It cannot be used by a function to store data that is longer than the function's running cycle. You have to give it to the heap, and then say goodbye to the hotspot cache, the explicit instantaneous operation, and the frequently calculated offsets. The upside is that it's working:
The price is that you have to remember to go to free () memory, or by a garbage collection mechanism that takes some performance to recycle randomly, garbage collection will go to find unused heap objects and then reclaim them. That's essentially the tradeoff between stacks and heaps: performance vs. flexibility.
Most programming language virtual machines have a middle tier to do something that a C programmer should do. Stacks are used for value types, such as integers, floating-point numbers, and Booleans. These are stored directly in the local variable and object fields by the byte order of a specific value (like the argc above). In contrast, heaps are used for reference types, such as strings and objects. Variables and fields contain a memory address referenced to this object, like song and band above.
Refer to this JavaScript function:
function fn ()
{
var a = ten;
var b = {name: ' foo ', N:10};
}
It may result in the following:
The reason I say "possible" is that a particular behavior is highly dependent on implementation. Many of the graphs used in this article are a V8-centric approach, which links to the relevant source code. In V8, only small integers are saved in the form of a value. So, from now on, I'm going to show the object as a string directly to avoid causing confusion, but remember, as shown in the previous illustration, they are saved separately in the heap.
Now, let's take a look at the closure, it's actually very simple, but because we're exaggerating it so much that it's a bit of a myth. Let's look at a simple JS function:
function Add (A, B)
{
var c = a + B;
return c;
}
This function defines a lexical domain (lexical scope), which is a happy little kingdom, where its name a,b,c has a definite meaning. It has two parameters and a local variable declared by the function. The program can also use the same name elsewhere, but within add the content they reference is clear. Although the lexical domain is a good term, it conforms to our intuitive understanding that, after all, we can literally think of it as a textual block in the source code like a lexical parser.
After seeing the stack frame operation, it is easy to imagine the specific implementation of this name. Inside the Add, these names refer to the location of the private stack in each running instance of the function. This situation occurs frequently in a virtual machine.
Now, let's Nest two lexical domains:
function Makegreeter ()
{
return function hi (name) {
console.log (' Hi, ' + name);
}
}
var hi = Makegreeter ();
Hi (' Dear reader '); Prints "Hi, dear Reader"
That's more fun. The function hi is built inside the function makegreeter when it is running. It has its own lexical domain, name in this place is a parameter on the stack, but it also seems to have access to the parent's lexical domain, which it can do. Let's take a look at the benefits of doing that:
function Makegreeter (greeting)
{
return function greet (name) {
Console.log (greeting + ', ' + name);
}
}
var Heya = makegreeter (' Heya ');
Heya (' Dear reader '); Prints "Heya, dear Reader"
I'm not used to it, but it's cool. Even if this violates our intuition: greeting does look like a stack variable, and this type should disappear after Makegreeter () returns. But because greet () kept working, something strange happened. Enter closures:
The virtual machine allocates an object to save the parent variable that is used by the greet () inside. It's like it's makegreeter. The lexical scope is closed at that moment, and is materialized to a heap object (in this case, the life cycle of the returned function) as needed. So it's called a closure, and when you think about it like that, its name makes sense. If more parent variables are used (or captured), the object content will have more properties, one for each captured variable. Of course, the code sent to greet () knows to read the greeting from the object's contents, not from the stack.
Here's a complete example:
function Makegreeter (Greetings)
{
var count = 0;
var greeter = {};
for (var i = 0; i < greetings.length; i++) {
var greeting = greetings[i];
Greeter[greeting] = function (name) {
count++;
Console.log (greeting + ', ' + name);
}
}
Greeter.count = function () {return count;}
return greeter;
}
var greeter = makegreeter (["Hi", "Hello", "Howdy"])
greeter.hi (' poppet ');//prints "Howdy, poppet"
Greeter.hello (' darling '),//prints "Howdy, darling"
greeter.count ();//returns 2
Yes, count () is working, but our greeter is on the stack in howdy. Can you tell me why. We use count as a clue: Although the lexical domain is closed in a heap object, the values of the variables (or object properties) may still be changed. The following figure is what we have:
This is a public content that is shared by all functions. That's why count works. However, greeting is also shared, and it is set to the last value after the end of the iteration, in this case "howdy". This is a common general error, and the simple way to avoid it is to refer to a function call with a closure variable as a parameter. In Coffeescript, the Do command provides a simple way to accomplish this. Here is a simple solution to our greeter:
function Makegreeter (Greetings)
{
var count = 0;
var greeter = {};
Greetings.foreach (function (greeting) {
greeter[greeting] = function (name) {
count++;
Console.log (greeting + ', ' + name);
}
});
Greeter.count = function () {return count;}
return greeter;
}
var greeter = makegreeter (["Hi", "Hello", "Howdy"])
greeter.hi (' poppet ');//Prints "Hi, poppet"
Greeter.hello (' darling '); Prints "Hello, darling"
greeter.count ();//returns 2
It is now working, and the result will become as shown in the following figure:
There are many arrows here. The feature we're interested in here is that in our code, we've closed down two nested lexical content, and it's perfectly guaranteed that we've got two of the object content linked to the heap. You can nest and close any lexical content, the "Russian Doll" type, and ultimately you are using a linked list of all those object content.
Of course, there are many ways to implement the features of these programming languages, just as they are inspired by carrier pigeons to implement TCP. For example, the ES6 specification defines a lexical environment as part of an environment record (roughly equivalent to a local identity within a block), plus a record that is linked to an external environment, allowing us to see the nesting. Logical rules are determined by the specification (a hope), but their implementation depends on converting them into bits and bytes.
You can also check the assembly code generated by V8 in a specific case. Vyacheslav Egorov has a good article that explains this process in detail using the V8 's closure internals. I have just started to learn V8, therefore, welcome advice. If you are familiar with C #, checking the intermediate code produced by closures will be very enlightening-you will see explicitly defined V8 content and instantiated simulations.
Closures are a powerful "guy." It provides a simple way to hide information from callers during a set of functions being shared. I like them to really hide your data: Unlike object fields, callers are not able to access or even see closure variables. Keep the interface clear and secure.
However, they are not "silver Bullets" (a translator's note: An extremely effective solution, or a new technology with high hopes). Sometimes a supporter of an object and a fan of closures will endlessly argue about their merits. Like most technical discussions, they tend to focus more on self-esteem than on real trade-offs. Anyway, Anton van Straaten's epic case solved the problem:
The venerable teacher Qc Na and its students Anton together for a walk. Anton hope to introduce the teacher into a discussion, Anton said: "Teacher, I heard the object is a very good thing, is this it." Qc Na took a sympathetic look at the student, blaming it, and said, "poor child-the object is nothing but the poor man's closure." After Anton's teacher left, he went back to his room and focused on learning how to close the bag. He carefully read the complete "lambda:the Ultimate ..." Series of articles and related data, and implemented a small architecture interpreter using a closed-packet object system. He learned a lot of things and looked forward to telling the teacher about his progress. In another walk with Qc Na, Anton tried to leave a good impression on the teacher, saying, "Teacher, I have studied this problem carefully, and now understand that the object is really the poor person's closure." "Qc Na used its cane to hit a bit Anton said:" When you will understand. Closures are the object of the poor. "At that time, Anton an epiphany. Anton van Straaten said: "The original structure is so cool ah." ”
This is the end of the quest "Stacks" article series.