لا
أ
N وبدعم
جهاز
هو جهاز كمبيوتر تورينج 16 بت مكافئ مصنوع بالكامل من ساعة وبوابات NAND تمت محاكاتها على الويب. تتميز NAND بوحدة المعالجة المركزية الخاصة بها، ولغة كود الآلة، ولغة التجميع، والمجمع، ولغة الآلة الافتراضية، ومترجم الآلة الافتراضية، ولغة البرمجة، والمترجم، وIDE، وواجهة المستخدم. تعتمد NAND على منصة Jack-VM-Hack المحددة في دورة Nand to Tetris والكتاب المرتبط بها.
برنامج بسيط يقوم بإدخال بعض الأرقام وحساب متوسطها، مع إظهار تدفق التحكم والعمليات الحسابية والإدخال والإخراج وتخصيص الذاكرة الديناميكية.
مخرجات البرنامج:
How many numbers? 4
Enter a number: 100
Enter a number: 42
Enter a number: 400
Enter a number: 300
The average is 210
تم توفير هذا البرنامج بواسطة مجموعة برامج Nand to Tetris.
لعبة بونغ، تظهر نموذج اللغة الشيئي. استخدم مفاتيح الأسهم لتحريك المجداف إلى اليسار واليمين لترتد الكرة. مع كل ارتداد، يصبح المجداف أصغر، وتنتهي اللعبة عندما تضرب الكرة الجزء السفلي من الشاشة.
تم توفير هذا البرنامج بواسطة مجموعة برامج Nand to Tetris.
لعبة 2048، تُظهر التكرار ومنطق التطبيق المعقد. استخدم مفاتيح الأسهم لتحريك الأرقام حول شبكة 4x4. يتم دمج نفس الأرقام في مجموعها عند نقلها إلى بعضها البعض. بمجرد الوصول إلى البلاط 2048، ستفوز باللعبة، على الرغم من أنه يمكنك الاستمرار في اللعب حتى تخسر. ستخسر اللعبة عندما تكون اللوحة ممتلئة ولا يمكنك القيام بأي تحركات أخرى.
برنامج يتسبب عمدًا في تجاوز سعة المكدس من خلال التكرار اللانهائي لتنفيذ عملية هروب من الجهاز الظاهري. إنه يعزز حقيقة عدم وجود فحوصات وقت التشغيل لمنع تجاوز سعة المكدس. لن تسمح لك أي منصة حديثة أخرى بالقيام بذلك :-)
عند التشغيل، سيقوم البرنامج باستمرار بطباعة مؤشر المكدس على الشاشة. بمجرد أن تتجاوز هذه القيمة المعروضة 2048، ستصل الحزمة إلى نهاية مساحة الذاكرة المقصودة وتنسكب على مساحة ذاكرة الكومة، مما يتسبب في خلل في عبارة الطباعة بطريقة متفجرة:
هناك شيئان جديران بالاهتمام جديران بالإشارة إليهما.
إذا قمت بتشغيل هذا البرنامج على ذاكرة وصول عشوائي فارغة مليئة بالأصفار (يمكنك مسح ذاكرة الوصول العشوائي من خلال واجهة المستخدم)، ستلاحظ أن البرنامج يعيد ضبط نفسه في منتصف عملية التنفيذ على الرغم من عدم الضغط على زر "إعادة الضبط". سبب حدوث ذلك بسيط: ينفذ وقت تشغيل كسر الحماية تعليمات تحدد قيمة عداد البرنامج على 0، مما يخبر البرنامج بشكل فعال بالانتقال إلى التعليمات الأولى والبدء من جديد.
إذا قمت بتشغيل برنامج مثال GeneticAlgorithm ثم قمت بتشغيله مباشرة بعد ذلك، فإن البرنامج في حالة هياجه يقرأ ذاكرة الوصول العشوائي القديمة التي لم تتم الكتابة فوقها مطلقًا.
برنامج يستغل حقيقة أن وقت التشغيل لا يمنع تحطيم المكدس لاستدعاء وظيفة قد يتعذر الوصول إليها. من أجل فهم كيفية عمل ذلك، دعونا نتفحص هذا الرسم التوضيحي لتخطيط إطار مكدس NAND.
مأخوذة من كتاب Nand to Tetris.
إذا لم تكن على دراية بتخطيطات المكدس، فإليك الفكرة الرئيسية وراء هذا الاستغلال. عندما تعود دالة، فإنها تحتاج إلى معرفة المكان (عنوان ذاكرة تعليمات رمز الجهاز) الذي يجب أن تذهب إليه لمتابعة تدفق التنفيذ. لذلك، عند استدعاء الوظيفة لأول مرة، يتم تخزين عنوان الذاكرة هذا، إلى جانب بعض البيانات الأخرى غير المهمة، مؤقتًا على المكدس في منطقة الذاكرة المشار إليها باسم إطار المكدس كمرجع لمكان العودة. يصف الرسم التوضيحي الموقع الدقيق لعنوان المرسل هذا بالنسبة لاستدعاء الوظيفة، وهو موضع يمكن إجراء هندسة عكسية له.
يمكّن البرنامج المستخدم من استبدال عنوان ذاكرة واحد في ذاكرة الوصول العشوائي (RAM) بأي قيمة. من خلال وضع اثنين واثنين معًا، إذا قام المستخدم بالكتابة فوق عنوان الإرجاع لإطار مكدس بعنوان وظيفة أخرى، فإنه يكتسب بشكل أساسي القدرة على تنفيذ تعليمات برمجية عشوائية مضمنة في البرنامج.
في الواقع، إذا قمت بإدخال 267 كموقع للذاكرة و1715 كقيمة للكتابة فوقها، فقد تم إجراء هندسة عكسية لرقمين عن طريق فحص مساحة ذاكرة المكدس والمجمّع يدويًا، وسترى هذه الفكرة في العمل.
هذه ليست ثغرة أمنية فريدة من نوعها لـ NAND. وهو موجود في C كذلك! كم هو رائع!
صدق أو لا تصدق، من بين المكونات العديدة والمختلفة لـ NAND، استغرق تطوير هذا الجهاز بمفرده وقتًا أطول!
هذا البرنامج عبارة عن محاكاة للمخلوقات تستخدم التعلم الآلي البسيط. إنه يتبع سلسلة الذكاء الاصطناعي المشفرة (الجزء الأول والثاني) من Code Bullet. تأكد من مراجعة قناته، فهو يقدم بعض الأشياء الرائعة حقًا!
وأوضح ببساطة:
كل نقطة لها "عقلها" الخاص من نواقل التسارع، وهي تتطور للوصول إلى الهدف من خلال الانتقاء الطبيعي. في كل جيل، من المرجح أن يتم اختيار النقاط التي "تموت" بالقرب من الهدف لتكون الوالدين للجيل القادم. يؤدي التكاثر بطبيعته إلى حدوث طفرة في بعض أجزاء الدماغ، مما يحاكي التطور الطبيعي بشكل فعال.
ومع ذلك، هناك الكثير مما هو مرغوب فيه. نظرًا للأداء، فإن العامل الوحيد الذي تستخدمه النقاط للتطور هو قربها من الهدف عند الموت، مما يمنح خوارزمية الانتقاء الطبيعي إنتروبيا منخفضة. بسبب استخدام الذاكرة، هناك حدود أقل من مرضية لعدد النقاط وأحجام أدمغتها. وأخيرًا، نظرًا للتعقيد الفني، فإن إعادة وضع العوائق أثناء المحاكاة لا يضمن أن النقاط سيكون لها أدمغة كبيرة بما يكفي للوصول إلى الهدف. يتم تحديد أحجام الدماغ فقط في بداية البرنامج.
لقد استخدمت عددًا لا يحصى من تقنيات التحسين للالتفاف حول قيود الأجهزة التالية وجعل ذلك ممكنًا:
لتجنب الالتفاف حول الأمر، لقد تمسكت بتوثيق هذه التقنيات والرؤى الإضافية في قاعدة التعليمات البرمجية لهذا البرنامج للمهتمين.
قبل أن نبدأ، أهم التفاصيل التي يجب تذكرها حول كتابة البرامج في Jack هي أنه لا توجد أولوية للمشغل؛ ربما هذا هو سبب عدم عمل برنامجك.
على سبيل المثال، يجب عليك تغيير:
4 * 2 + 3
إلى (4 * 2) + 3
if (~x & y)
إلى if ((~x) & y)
ولكن يمكنك الاحتفاظ if (y & ~x)
كما هو لأنه لا يوجد غموض في عامل التشغيل.
بدون قوسين، تكون قيمة التقييم للتعبير الغامض غير محددة .
تفتخر NAND بمجموعة التكنولوجيا الكاملة الخاصة بها. ونتيجة لذلك، لا يمكن برمجة NAND إلا بلغة Jack، وهي لغة البرمجة الشيئية المكتوبة بشكل ضعيف. في مصطلحات الشخص العادي، جاك هو C مع بناء جملة جافا.
دعونا نتبع نهج التعلم المبني على الأمثلة ونتعمق فيه.
/**
* This program prompts the user to enter a phrase
* and an energy level. Program output:
*
* Whats on your mind? Superman
* Whats your energy level? 3
* Superman!
* Superman!
* Superman!
*/
class Main {
function void main ( ) {
var String s ;
var int energy , i ;
let s = Keyboard . readLine ( "Whats on your mind? " ) ;
let energy = Keyboard . readInt ( "Whats your energy level? " ) ;
let i = 0 ;
let s = s . appendChar ( 33 ) ; // Appends the character '!'
while ( i < energy ) {
do Output . printString ( s ) ;
do Output . println ( ) ;
let i = i + 1 ;
}
}
}
مأخوذة من شرائح محاضرة Nand إلى Tetris.
إذا كانت لديك بالفعل بعض الخبرة في البرمجة، فيجب أن يبدو هذا مألوفًا جدًا؛ من الواضح أن جاك كان مستوحى بشكل كبير من جافا. Main.main
، نقطة الدخول إلى البرنامج، توضح الاستخدام الأساسي للمتغيرات بالإضافة إلى حلقة while للتحكم في التدفق.
بالإضافة إلى ذلك، فهو يستخدم Keyboard.readLine
و Keyboard.readInt
لقراءة المدخلات من المستخدم، و Output.printString
و Output.println
لطباعة المخرجات على الشاشة - وكلها محددة بالتفصيل في Jack OS Reference. افتراضيًا، يتم تضمين نظام التشغيل Jack OS مع برنامجك أثناء الترجمة لتمكين التفاعل مع السلاسل والذاكرة والأجهزة والمزيد.
تحتوي كل لغة برمجة على مجموعة ثابتة من أنواع البيانات البدائية. يدعم جاك ثلاثة: int
و char
و boolean
. يمكنك توسيع هذه الذخيرة الأساسية باستخدام أنواع البيانات المجردة الخاصة بك حسب الحاجة. المعرفة المسبقة حول البرمجة الشيئية تنتقل مباشرة إلى هذا القسم.
/** Represents a point in 2D plane. */
class Point {
// The coordinates of the current point instance:
field int x , y ;
// The number of point objects constructed so far:
static int pointCount ;
/** Constructs a point and initializes
it with the given coordinates */
constructor Point new ( int ax , int ay ) {
let x = ax ;
let y = ay ;
let pointCount = pointCount + 1 ;
return this ;
}
/** Returns the x coordinate of the current point instance */
method int getx ( ) { return x ; }
/** Returns the y coordinate of the current point instance */
method int gety ( ) { return y ; }
/** Returns the number of Points constructed so far */
function int getPointCount ( ) {
return pointCount ;
}
/** Returns a point which is this
point plus the other point */
method Point plus ( Point other ) {
return Point . new ( x + other . getx ( ) ,
y + other . gety ( ) ) ;
}
/** Returns the Euclidean distance between the
current point instance and the other point */
method int distance ( Point other ) {
var int dx , dy ;
let dx = x - other . getx ( ) ;
let dy = y - other . gety ( ) ;
return Math . sqrt ( ( dx * dx ) + ( dy * dy ) ) ;
}
/** Prints the current point instance, as "(x, y)" */
method void print ( ) {
var String tmp ;
let tmp = "(" ;
do Output . printString ( tmp ) ;
do tmp . dispose ( ) ;
do Output . printInt ( x ) ;
let tmp = ", " ;
do Output . printString ( tmp ) ;
do tmp . dispose ( ) ;
do Output . printInt ( y ) ;
let tmp = ")" ;
do Output . printString ( tmp ) ;
do tmp . dispose ( ) ;
}
}
var Point p1 , p2 , p3 ;
let p1 = Point . new ( 1 , 2 ) ;
let p2 = Point . new ( 3 , 4 ) ;
let p3 = p1 . plus ( p2 ) ;
do p3 . print ( ) ; // prints (4, 6)
do Output . println ( ) ;
do Output . printInt ( p1 . distance ( p2 ) ) ; // prints 5
do Output . println ( ) ;
do Output . printInt ( getPointCount ( ) ) ; // prints 3
مأخوذة من شرائح محاضرة Nand إلى Tetris.
نحدد فئة Point
لتمثيل نقطة مجردة في الفضاء. يستخدم متغيرات field
للإعلان عن سمات نوع البيانات لكل مثيل. إنه يكشف عن وظائف method
العامة التي يمكننا استخدامها للتفاعل مع النقطة، مما يمنح المتصل وظيفة إضافة نقطتين معًا وحساب المسافة بين نقطتين.
يتم تحديد نطاق كافة متغيرات field
بشكل خاص. إذا كنت ترغب في الحصول على هذه المتغيرات أو تعيينها من خارج إعلان الفئة، فيجب أن تحتوي هذه المتغيرات على وظائف method
مقابلة لتوفير هذه الوظيفة.
تم حذفها من نموذج التعليمات البرمجية للبقاء على الموضوع، ومن المعتاد أن تحدد فئات البيانات طرق dispose
من إلغاء التخصيص بمجرد عدم الحاجة إلى الكائنات. راجع إدارة الذاكرة اليدوية.
إذا لزم الأمر، إليك مرجع لبناء جملة استدعاء function
method
.
class Foo {
...
method void f ( ) {
var Bar b ; // Declares a local variable of class type Bar
var int i ; // Declares a local variable of primitive type int
do g ( ) ; // Calls method g of the current class on the current object instance
// Note: Cannot be called from within a function (static method)
do Foo . p ( 3 ) ; // Calls function p of the current class;
// Note: A function call must be preceded by the class name
do Bar . h ( ) ; // Calls function h of class Bar
let b = Bar . r ( ) ; // Calls function or constructor r of class Bar
do b . q ( ) ; // Calls method q of class Bar on the b object
}
}
مأخوذة من شرائح محاضرة Nand إلى Tetris.
هل تتذكر كيف قلنا أن جاك يشبه جافا؟ لقد كانت تلك واجهة، أو في أحسن الأحوال مضللة. في حين أن Java مكتوبة بقوة وبالتالي تدعم ميزات الكتابة المعقدة مثل الصب وتعدد الأشكال والميراث، فإن Jack لا يدعم أيًا من هذه ولديه نوع واحد فقط تحت الغطاء: العدد الصحيح 16 بت الموقع. هذا هو السبب الرئيسي وراء ضعف كتابة جاك. في الواقع، لن يهتم المترجم إذا قمت بخلط ومطابقة أنواع مختلفة في المهام والعمليات.
var char c ;
var String s ;
let c = 65 ; // 'A'
// Equivalently
let s = "A" ;
let c = s . charAt ( 0 ) ;
var Array a ;
let a = 5000 ;
let a [ 100 ] = 77 ; // RAM[5100] = 77
var Array arr ;
var String helloWorld ;
let helloWorld = "Hello World!"
let arr = Array . new ( 4 ) ; // Arrays are not strictly typed
let arr [ 0 ] = 12 ;
let arr [ 1 ] = false ;
let arr [ 2 ] = Point . new ( 5 , 6 ) ;
let arr [ 3 ] = helloWorld ;
class Complex {
field int real ;
field int imaginary ;
...
}
. . .
var Complex c ;
var Array a ;
let a = Array . new ( 2 ) ;
let a [ 0 ] = 7 ;
let a [ 1 ] = 8 ;
let c = a ; // c == Complex(7, 8)
// Works because it matches the memory layout
// of the Complex type
جميع مقاطع التعليمات البرمجية المأخوذة من شرائح محاضرات Nand إلى Tetris.
لا تفهم هذا بطريقة خاطئة - لا يزال جاك يقدم نموذجًا قويًا وعمليًا موجهًا للكائنات. تهدف هذه الرؤية إلى مساعدتك على فهم متى وكيف يجب عليك إجراء تحويلات الكتابة حسب الحاجة.
لنفترض أنك عاشق مجنون للقطط، مثلي تمامًا! وأردت أن تكتب هذا البرنامج لتظهر مدى حبك للقطط.
class Main {
function void main ( ) {
while ( true ) {
do Output . printString ( "Kittens are so adorable! " ) ;
}
}
}
قد تتفاجأ عندما تلاحظ أنه بعد بضع ثوانٍ، سيتعطل البرنامج مع ظهور "ERR6"، أو تجاوز سعة الكومة!
جاك هي لغة برمجة تدار بالذاكرة يدويًا. هذا يعني أنه يجب عليك توخي الحذر لإلغاء تخصيص الذاكرة التي لم تعد هناك حاجة إليها بشكل صحيح، وإلا فإن نظام التشغيل Jack OS سوف يفكر بطريقة أخرى ويسهل تسرب الذاكرة. نصيحة أفضل الممارسات هي إبراز طريقة dispose
لكل فئة تمثل كائنًا يغلف عملية إلغاء التخصيص هذه بشكل صحيح. وبالتالي، عندما لا تكون هناك حاجة للكائنات، يمكنك استدعاء أساليب dispose
الخاصة بها للتأكد من عدم نفاد ذاكرة الكومة في النهاية.
إذا كنت قد برمجت بلغات أخرى تديرها الذاكرة يدويًا، مثل لغة C، فيجب أن يبدو هذا مألوفًا جدًا. أحد الاختلافات الرئيسية هو أن نظام التشغيل Jack OS يخزن المصفوفات والسلاسل على الكومة بدلاً من المكدس، مما يشير إلى سبب تعطل البرنامج مع تجاوز سعة الكومة.
دعونا نصلح هذا البرنامج لزملائنا المتعصبين للقطط.
class Main {
function void main ( ) {
var String s ;
while ( true ) {
let s = "Kittens are so adorable! " ;
do Output . printString ( s ) ;
do s . dispose ( ) ;
}
}
}
وبدلاً من ذلك، يمكنك تخصيص الذاكرة للسلسلة مرة واحدة فقط.
class Main {
function void main ( ) {
var String s ;
let s = "Kittens are so adorable! " ;
while ( true ) {
do Output . printString ( s ) ;
}
}
}
ستلاحظ أن هذه الإصدارات البديلة لا تطبع السلسلة بشكل أسرع فحسب، بل ستطبع هذه المرة إلى الأبد! مرحا!
دعنا نلقي نظرة سريعة على String.dispose
حتى تتمكن من فهم كيفية كتابة أساليب dispose
الخاصة بك بشكل أفضل.
method void dispose ( ) {
do stringArray . dispose ( ) ;
do Memory . deAlloc ( this ) ;
}
تم استدعاء Array.dispose
بواسطة stringArray
method void dispose ( ) {
do Memory . deAlloc ( this ) ;
}
يجب أن تقوم أساليب dispose
المناسبة أولاً باستدعاء dispose
من متغيرات الحقول الخاصة بها بشكل مناسب ثم تنتهي باستخدام do Memory.deAlloc(this);
لإلغاء تخصيص مثيل الكائن نفسه.
مع مدى بدائية جاك وناند، فإن وجود بنادق داخل اللغة أمر لا مفر منه. لقد قمت بتجميع الأمثلة التالية للسلوك غير المحدد الذي يجب أن تكون على دراية به، مرتبة من (في رأيي) الأكثر أهمية إلى الأقل أهمية.
لقد وجدت أن هذا التحذير مهم جدًا لدرجة أنني قمت بنقله إلى بداية هذا القسم.
تعابير جاك
a > b
a < b
بسيطة بشكل مخادع. وهي ليست دائمًا صحيحة رياضيًا، وهي تعادل على التوالي تعبيرات Java
( ( a - b ) & ( 1 << 15 ) ) == 0 && a != b
( ( a - b ) & ( 1 << 15 ) ) != 0
ما الأمر مع فارق بسيط؟ يقوم تطبيق الجهاز الظاهري بتحويل a > b
إلى a - b > 0
. ها هي المشكلة: a - b
يمكن أن يتجاوز :(
ما قيمة 20000 > -20000
؟ يقوم الجهاز الظاهري بنقل هذا إلى 20000 - (-20000) > 0
والذي يتم تقييمه إلى -25336 > 0
. لسوء الحظ، الجواب false
.
ومع ذلك، يتم تقييم 20000 > -10000
إلى 30000 > 0
، أو true
.
كما قد تكون ظننت، إذا كانت المسافة المطلقة بين a
و b
أكبر من 32767، فإن a > b
و a < b
خاطئان. خلاف ذلك، أنت بخير.
هذا ليس خطأ في التنفيذ، بل هو عدم تناسق بين Nand وTetris نفسها. المزيد عنها هنا لأسباب تتعلق بالتوافق، لن يتم إصلاح هذا السلوك.
-32768 هو واحد من نوعه. إنه الرقم الوحيد الذي يحمل الخاصية بحيث -(-32768) = -32768، رقم مفرد بدون نظير موجب * . وهذا يمكن أن يؤدي إلى عدم السلامة والأخطاء المنطقية.
/**
* Program output:
* --.)*(
*/
class Main {
function void main ( ) {
// Note that -32768 must instead be written as ~32767
// because the CPU can't load a number that large
do Output . printInt ( ~ 32767 ) ;
}
}
يتوقع Output.printInt
داخليًا أن يقوم Math.abs
بإرجاع رقم موجب. هذا ليس هو الحال مع -32768، لذلك يتعطل نظام التشغيل Jack OS.
يجب أن يكون اهتمامك الرئيسي هو التعامل مع الأخطاء المنطقية باستخدام عامل التشغيل السالب. باعتبارك مبرمجًا، إذا كنت تريد التأكد من أن الرقم السالب موجب، فمن مسؤوليتك التحقق من حالة -32768 واتخاذ الإجراء المناسب.
* هذا صحيح لأن ALU الخاص بـ NAND يعالج داخليًا تعبير Jack -x
كـ ~(x - 1)
. لنقم بتعيين x
على -32768
وتقييمه خطوة بخطوة. فيما يلي التمثيلات الثنائية المكملة للحساب ذات 16 بت اثنين:
x
= 1000 0000 0000 0000
x - 1
= 0111 1111 1111 1111
~(x - 1)
= 1000 0000 0000 0000
= x
إنه نفس الشيء! ماذا حدث هنا؟ نظرًا لأن NAND عبارة عن جهاز 16 بت، فإن -32768 هو الرقم الوحيد الذي إذا قمت بطرح واحد منه، فستحصل على البتات المعكوسة. بمعنى آخر، -32768 يرضي x - 1 = ~x
، مما يبسط التعبير إلى ~(~x)
أو x
فقط.
هذا لا يحتاج إلى شرح، لذا إليك عرض توضيحي مختصر.
/**
* Program output:
* I have 818 cookies.
*/
class Main {
function void main ( ) {
do Main . cookies ( ) ;
}
function void cookies ( int a ) {
do Output . printString ( "I have " ) ;
do Output . printInt ( a ) ;
do Output . printString ( " cookies." ) ;
}
}
ومن ناحية أخرى، فإن استدعاء دالة تحتوي على عدد كبير جدًا من الوسائط يعد أمرًا صالحًا تمامًا. يمكنك استخدام الكلمة الأساسية arguments
لفهرسة وسائط الوظائف الإضافية. لاحظ أنه لا يوجد مؤشر لعدد الوسائط.
يمكنك استخدام Array
لإرسال متغير إلى أي نوع آخر. يعد استدعاء أساليب المثيل غير الموجودة في متغيرات النوع سلوكًا غير محدد؛ المترجم ليس ذكيًا بما يكفي لإدراك متى تفعل هذا.
/**
* Program output:
* 4
*/
class Main {
constructor Main new ( ) {
return this ;
}
function void main ( ) {
var Array a ;
var Main b ;
var String c ;
let a = Array . new ( 1 ) ;
let b = Main . new ( ) ;
let a [ 0 ] = b ;
let c = a [ 0 ] ;
// Invalidly calling `String.length` on an instance of `Main`.
do Output . printInt ( c . length ( ) ) ;
}
}
راجع برنامج Overflow للحصول على مثال متعمق.
قد يؤدي تعديل إطارات المكدس أو السجلات الداخلية الموجودة على التوالي في عناوين الذاكرة 256
إلى 2047
ومن 1
إلى 15
إلى سلوك غير محدد. لا يكون هذا ممكنًا عادةً دون إساءة استخدام Memory.poke
أو فهرسة المصفوفة السلبية. راجع برنامج SecretPassword للحصول على مثال متعمق.
تقدم NAND التحقق من صحة البرنامج لملفات .jack
ولكن ليس لملفات .vm
. وهذا يعني أن برنامج التحويل البرمجي للجهاز الظاهري الخاص بـ NAND يمنحك حرية لاستدعاء وظائف غير موجودة، أو الإشارة إلى المتغيرات غير المعينة، أو إجراء أي عملية ذاكرة أخرى غير صالحة منطقيًا. في معظم حالات هذا السلوك غير المحدد، سيهرب الجهاز الظاهري ولن تعرض الشاشة أي شيء. ستكون مسؤوليتك تصحيح أخطاء البرنامج بنفسك.
منذ ظهورها في السبعينيات، هناك سبب وجيه وراء تراجع الحوسبة ذات 16 بت في العصر الحديث. بالمقارنة مع الحوسبة 32 بت أو 64 بت، قدمت الحوسبة 16 بت قوة معالجة وسعة ذاكرة محدودة لا تلبي ببساطة متطلبات البرامج والتطبيقات المعاصرة.
NAND ليست استثناءً من هذا الواقع.
مأخوذة من شرائح محاضرة Nand إلى Tetris.
بالمقارنة مع جهاز MacBook الخاص بك الذي تبلغ سعته 16 جيجا بايت، تتمتع NAND بذاكرة وصول عشوائي (RAM) ضئيلة تبلغ 4 كيلو بايت، أي بنسبة 0.00002% ! وعلى الرغم من ذلك، كان ذلك كافيًا لأخذنا إلى القمر، فمن يستطيع أن يقول أن NAND لا تستطيع ذلك أيضًا.
يحتفظ نظام التشغيل Jack OS بـ 14336 عنوانًا للذاكرة