Function definition
Function application
Global scope
Grammar
- Expr ::= ...
- | 'def' Ident '=' Expr
- | Ident '()'
Function definition
Function application
global accesses the global scope- def useX():
- print (x)
- def defX():
- global x
- x = 1
- >>> useX()
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "<stdin>", line 2, in useX
- NameError: name 'x' is not defined
- >>> defX()
- >>> useX()
- 1
What does this program do?
- var x:Int = 10
- def f () =
- x = 20
- def g () =
- var x:Int = 30
- f ()
- g ()
- println (x)
Static Scope
Dynamic Scope
z come from?- ...
- def g (x:Int) : Int =
- var y:Int = x * 2
- z * x * y // x and y are bound; z is free
global) etc.- var x:Int = 10
- def f () : Unit =
- x = 20
- def g () : Unit =
- var x:Int = 30
- f ()
- g ()
- println (x) // prints 20
- x=10
- function f() {
- x=20
- }
- function g() {
- local x=30
- f
- }
- g
- echo $x # prints 10
Dynamic Scope in Emacs Lisp
- (let ((x 10))
- (defun f ()
- (setq x 20))
- (defun g ()
- (let ((x 30))
- (f)))
- (g)
- x ; x is now 10
- )
Static Scope in Common Lisp
- (let ((x 10))
- (defun f ()
- (setq x 20))
- (defun g ()
- (let ((x 30))
- (f)))
- (g)
- x ; x is now 20
- )
local- local $x = 10;
- sub f {
- $x = 20;
- }
- sub g {
- local $x = 30;
- f ();
- }
- g ();
- print ($x); # prints 10
my- my $x = 10;
- sub f {
- $x = 20;
- }
- sub g {
- my $x = 30;
- f ();
- }
- g ();
- print ($x); # prints 20
- static void f () {
- int x = 1;
- {
- int y = x + 1;
- {
- int x = y + 1;
- System.out.println ("x = " + x);
- }
- }
- }
- $ javac C.java
- C.java:7: error: variable x is already defined in method f()
- int x = y + 1;
- ^
- 1 error
- public class C {
- static int x = 1;
- static void f () {
- int y = x + 1;
- {
- int x = y + 1;
- System.out.println ("x = " + x);
- }
- }
-
- public static void main (String[] args) {
- f ();
- }
- }
- $ javac C.java
- $ java C
- x = 3
- int main () {
- int x = 1;
- {
- int y = x + 1;
- {
- int x = y + 1;
- printf ("x = %d\n", x);
- }
- }
- }
- $ gcc -o scope scope.c
- $ ./scope
- x = 3
- def main (args:Array[String]) =
- var x = 1
- var y = x + 1
- var x = y + 1
- println ("x = " + x)
- end main
- $ scalac C.scala
- $ scala C
- x = 3
- def sum(x: List[Int]) = x match
- case Nil => 0
- case x :: tail => x + sum(tail)
Is x in scope?
- int main (void) {
- int x = 10;
- {
- int x = x + 1;
- printf ("x = %08x\n", x);
- }
- return 0;
- }
- $ gcc -o scope scope.c
- $ gcc -Wall -o scope scope.c
- scope.c: In function âmainâ:
- scope.c:5:7: warning: unused variable âxâ [-Wunused-variable]
- scope.c:7:9: warning: âxâ is used uninitialized in this function [-Wuninitialized]
- $ ./scope
- x = 00000001
- public static void main (String[] args) {
- int x = x + 1;
- System.out.printf ("x = %08x\n", x);
- }
- x.java:3: error: variable x might not have been initialized
- int x = x + 1;
- ^
0) before the initialization code is runScala
- scala> val x:Int = 1 + x
- x: Int = 1
Expressed in Java
- public class C {
- private final int x; // default-initialized to 0
- public int x() { return x; }
- public C() { x = 1 + x; }
- }
Does that work with complex datatypes?
- val xs:List[Int] = 1 :: xs
- // java.lang.NullPointerException
xs default-initialized to nullnull != Nil: exception occurs because 1 :: null is null.::(1)#:: are non-strict- val ones:LazyList[Int] = 1 #:: ones
- // ones: LazyList[Int] = LazyList(<not computed>)
- scala> ones.take (5)
- res0: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>)
- scala> ones.take (5).toList
- res1: List[Int] = List(1, 1, 1, 1, 1)
- def f (x:Int) : LazyList[Int] =
- println (s"Called f($x)")
- x #:: f(x+1)
- end f
- // f: (x: Int): LazyList[Int]
- scala> val xs:LazyList[Int] = f(10)
- Called f(10)
- xs: LazyList[Int] = LazyList(<not computed>)
Access elements of lazy list
- scala> xs.take(4).toList
- Called f(11)
- Called f(12)
- Called f(13)
- res12: List[Int] = List(10, 11, 12, 13)
Access same elements and more
- scala> xs.take(4).toList
- res13: List[Int] = List(10, 11, 12, 13)
- scala> xs.take(6).toList
- Called f(14)
- Called f(15)
- res14: List[Int] = List(10, 11, 12, 13, 14, 15)
Recursion
Corecursion
Scope
Lifetime
What is wrong with this program?
- #include <stdio.h>
- #include <stdlib.h>
- int *f (int x) {
- int y = x;
- return &y;
- }
- int main (void) {
- int *p = f (1);
- printf ("*p = %d\n", *p);
- return 0;
- }
- $ gcc -o ar ar.c
- ar.c: In function âfâ:
- ar.c:6:3: warning: function returns address of local variable
- [enabled by default]
- $ ./ar
- *p = 1
- $ valgrind ./ar
- ==5505== Memcheck, a memory error detector
- ==5505== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
- ==5505== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
- ==5505== Command: ./ar
- ==5505==
- ==5505== Conditional jump or move depends on uninitialised value(s)
- ==5505== at 0x4E7C1A1: vfprintf (vfprintf.c:1596)
- ==5505== by 0x4E85298: printf (printf.c:35)
- ==5505== by 0x400536: main (in /tmp/ar)
- ==5505==
- ==5505== Use of uninitialised value of size 8
- ==5505== at 0x4E7A49B: _itoa_word (_itoa.c:195)
- ==5505== by 0x4E7C4E7: vfprintf (vfprintf.c:1596)
- ==5505== by 0x4E85298: printf (printf.c:35)
- ==5505== by 0x400536: main (in /tmp/ar)
- ==5505==
- ==5505== Conditional jump or move depends on uninitialised value(s)
- ==5505== at 0x4E7A4A5: _itoa_word (_itoa.c:195)
- ==5505== by 0x4E7C4E7: vfprintf (vfprintf.c:1596)
- ==5505== by 0x4E85298: printf (printf.c:35)
- ==5505== by 0x400536: main (in /tmp/ar)
- ==5505==
- *p = 1
- ==5505==
- ==5505== HEAP SUMMARY:
- ==5505== in use at exit: 0 bytes in 0 blocks
- ==5505== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
- ==5505==
- ==5505== All heap blocks were freed -- no leaks are possible
- ==5505==
- ==5505== For counts of detected and suppressed errors, rerun with: -v
- ==5505== Use --track-origins=yes to see where uninitialised values come from
- ==5505== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 2 from 2)
What is wrong with this program?
- #include <stdio.h>
- #include <stdlib.h>
- int *f (int x) {
- int *result = (int *) malloc (sizeof (int));
- *result = x;
- return result;
- }
- int main (void) {
- int *p = f (1);
- printf ("*p = %d\n", *p);
- return 0;
- }
- $ gcc -Wall -o ar ar.c && ./ar
- *p = 1
- $ valgrind ./ar
- ==10962== Memcheck, a memory error detector
- ==10962== Copyright (C) 2002-2011, and GNU GPL, by Julian Seward et al.
- ==10962== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
- ==10962== Command: ./ar
- ==10962==
- *p = 1
- ==10962==
- ==10962== HEAP SUMMARY:
- ==10962== in use at exit: 4 bytes in 1 blocks
- ==10962== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
- ==10962==
- ==10962== LEAK SUMMARY:
- ==10962== definitely lost: 4 bytes in 1 blocks
- ==10962== indirectly lost: 0 bytes in 0 blocks
- ==10962== possibly lost: 0 bytes in 0 blocks
- ==10962== still reachable: 0 bytes in 0 blocks
- ==10962== suppressed: 0 bytes in 0 blocks
- ==10962== Rerun with --leak-check=full to see details of leaked memory
- ==10962==
- ==10962== For counts of detected and suppressed errors, rerun with: -v
- ==10962== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
What is wrong with this program?
- #include <stdio.h>
- #include <stdlib.h>
- int *f (int x) {
- int *result = (int *) malloc (sizeof (int));
- *result = x;
- return result;
- }
- int main (void) {
- int *p = f (1);
- free (p);
- printf ("*p = %d\n", *p);
- return 0;
- }
- $ gcc -Wall -o ar ar.c && ./ar
- *p = 0
- $ valgrind ./ar
- ==13594== Memcheck, a memory error detector
- ==13594== Copyright (C) 2002-2011, and GNU GPLd, by Julian Seward et al.
- ==13594== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
- ==13594== Command: ./ar
- ==13594==
- ==13594== Invalid read of size 4
- ==13594== at 0x4005D2: main (in /tmp/ar)
- ==13594== Address 0x51f0040 is 0 bytes inside a block of size 4 freed
- ==13594== at 0x4C2A82E: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
- ==13594== by 0x4005CD: main (in /tmp/ar)
- ==13594==
- *p = 1
- ==13594==
- ==13594== HEAP SUMMARY:
- ==13594== in use at exit: 0 bytes in 0 blocks
- ==13594== total heap usage: 1 allocs, 1 frees, 4 bytes allocated
- ==13594==
- ==13594== All heap blocks were freed -- no leaks are possible
- ==13594==
- ==13594== For counts of detected and suppressed errors, rerun with: -v
- ==13594== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
Scope
Lifetime
# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Call Stack of Activation Records - _Call stack_ of ARs allows - fast _allocation_ of fresh AR on function call - fast _deallocation_ of AR on function return - Contrast with heap allocation - _Stack discipline_ ensures ordering of AR - (call f) allocate AR for f - (call g) allocate AR for g - (return from g) deallocate AR for g - (return from f) deallocate AR for f ---
# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Call Stacks in Multi-Threaded Applications :fa fa-question-circle: How should we maintain activation records in multi-threaded applications? * Each thread needs a separate call stack * Calls and returns in separate threads are independent ---
# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Heap Allocation - Heap allocation can use any allocation pattern (not strict like stack discipline) - For example, allocate _M_ bytes :fa fa-arrow-right: allocate _N_ bytes :fa fa-arrow-right: deallocate _M_ bytes :fa fa-arrow-right: deallocate _N_ bytes - Allocations may be long-lived, others short-lived - Gives freedom, but more costly than call stack ---
# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-book fa-stack-1x fa-inverse"></i></span> Common Problems - PLs with garbage collection - Java, Scala, C#, Python, Ruby, JS, Scheme, etc. * Lifetime too long (not GCed) - PLs with manual memory management - C, C++ * Pointers to storage whose lifetime has ended * _Dangling pointers_ to an old AR * _Dangling pointers_ to `free`d heap memory (_use after free_) * Double `free`ing of heap memory ---