首页 > > 详细

COMP 2012辅导、辅导Programming编程、辅导c/c++程序设计辅导R语言程序|辅导Web开发

2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
COMP 2012 Object-Oriented Programming and Data
Structures
Assignment 2 Assembly Emulator
Introduction
In this assignment, you will be practicing the following concepts:
Inheritance
Polymorphism
Pointers and References
Abstract Base Class
Update (Mar 20 0030):
Since some students have issues with the library archive
version of the assignment, we will switch to using the source-only version from now
on. Please check the Download section for the latest skeleton code.
For those of you who have already started the assignment, you can download the new
skeleton code, and copy-paste the AST.h and InstructionAST.cpp directly into the new
skeleton code.
The recording of the briefing session can be found here, and the accompanying slides
can be found here.
We value academic integrity very highly. Please read the Honor Code section on our course
webpage to make sure you understand what is considered as plagiarism and what the
penalties are. The following are some of the highlights:
Do NOT try your "luck" - we use sophisticated plagiarism detection software to find
cheaters. We also review codes for potential cases manually.
The penalty (for BOTH the copier and the copiee) is not just getting a zero in your
assignment. Please read the Honor Code thoroughly.
Serious offenders will fail the course immediately, and there may be additional
disciplinary actions from the department and university, upto and including expulsion.
Glossary
Menu
Introduction
Glossary
Background
Assignment Details
Download
Sample Output and
Grading Scheme
PA2 Q&A on Piazza
Submission &
Deadline
FAQ
Page maintained by
MAK, Ching Hang
(David)
Email:
chmakac@connect.ust.
hk
Last Modified:
03/21/2021 184956
Homepage
Course Homepage
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 2/15
This section will mainly cover the key terminology used in the rest of this description.
Assembly Language
Assembly language generally refer to low-level programming languages which can be
mapped one-to-one to instructions understood by your processor. Unlike C++ (which needs
to be compiled), assembly can be directly executed on your processor; In fact, C++ is
compiled into assembly.
Instruction Set Architecture
An Instruction Set Architecture (or ISA for short) is a definition of the internals of a
processors. This usually includes the list of instructions the architecture needs to support,
how memory should be addressed, and how many and what kind of registers must be
present.
Currently, most desktop and laptop computers use x86-64 (developed by Intel and
augmented by AMD), whereas most mobile devices use AArch64 (developed by ARM).
Those of you who have taken COMP2611 will also know MIPS.
Opcode
Opcodes (Operation code) are names for instructions which describe what the operation
does. You can think of an opcode as a function name in C++.
Operand
Operands are parts of an instruction which specifies the input/output of the instruction. You
can think of each instruction as a function call in C++; the operands will then be the
function's formal parameters (arguments).
Register
Registers are small chunks of very fast memory residing within the processor; Instructions
usually operate on registers.
Stack
A program stack is a chunk of memory which stores variables within a function.
Abstract Syntax Tree (AST)
An abstract syntax tree is a tree which represents the syntactic structure of an expression.
For example, the instruction mov r11, sp will generate the following AST:
MovInstruction
|-RegisterOperand r11
|-RegisterOperand sp/r13
Since mov is an instruction which has two operands, under MovInstruction there are two
RegisterOperands.
The following shows a table of corresponding concepts between our flavor of assembly and
C++.
Assembly C++
Opcode Function Name
Instruction Function Call
Operands Formal Parameters (Arguments)
Register A 32-bit memory residing within the processor.
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 3/15
Background: Emulators & Processors
In this assignment, you will be implementing a small part of an ARM emulator. Emulation is
usually used when you want your machine to "pretend" to be another machine. For example,
mobile application developers use emulators to run a version of Android or iOS on their local
machine for development.
Since an emulator is effectively a "software processor", we will need to replicate some parts
of the processor in our emulator.
Note: You do not need to implement everything stated below, it is just an overview for
you to understand the parts that have been implemented for you (and that you will
need to use).
Assembly?
As you may know, your compiler compiles C++ source code into assembly code. Since the
flavor of assembly is depedent on the processor it is executed on, different compilers on
different platforms running different processors will generate different assembly code.
To demonstrate this example, consider the following C++ snippet.
This will be compiled to the following ARMv7-A assembly (the ISA we will be emulating).
Note that @ are comments, similar to // in C++:
getInt():
mov r0, #0 @ Set 0 as the return value
bx lr @ Return from getInt()
main:
push {lr} @ Store the link-register into the stack;
@ lr will be overwritten in the next
instruction
bl getInt() @ Call getInt; Return value will be in r0
add r0, r0, #1 @ Add 1 to the value returned by getInt()
pop {lr} @ Load the link-register from the stack
@ This is to ensure we "return" to the
correct location
bx lr @ Return from main()
As you can see, a lot of operations are actually similar between C++ and Assembly, although
Assembly language is significantly more verbose. There are a lot more things you need to do,
for example, "spilling" register to the stack if we are jumping between functions, and many
"one-liners" in C++ are now done with multiple instructions.
Don't worry if you don't understand what each instruction means or does in the above
example. More detailed explanation will be given in the latter parts of this description.
Instructions
The most important functionality of a processor is to "perform some work"; These actions
are usually represented in processors as instructions. Instructions are single specific actions
which a processor is able to perform.
In assembly programs, instructions are usually represented as an opcode and operands. An
opcode is a name for an instruction, whereas operands are "things" that an instruction
operate on.
int getInt() { return 0; }
int main() {
return getInt() + 1;
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 4/15
For example, consider the following example:
add r1, r0, #1
add r1, r0, #1 represents an instruction.
add is the opcode.
r1, r0, #1 are all operands to the instruction.
Translating this to C++, this would be equivalent to:
void add(Register& op1, const Register& op2, unsigned op3);
add(r1, r0, 1);
In other words, using C++ terminology:
Opcodes are function names.
Instructions are function calls.
Operands are formal parameters (arguments).
In the above example, you will see that the first two operands are prefixed with r, whereas
the third operand is prefixed with #. These denote different types of operands.
r-prefixed operands represent registers (see below).
#-prefixed operands represent immediates, i.e. constant values.
Operands enclosed in square brackets ([]) represent indirect operands, i.e. a
dereferenced memory location.
For example, the operand [r0, #4] refers to the memory location pointed to by
register r0, offset by 4 (i.e. *((char*) r0 + 4) in C++).
Operands enclosed in curly brackets ({}) represent register lists, i.e. a non-empty list
of registers.
For example, the operand {r11, sp} represents a register list containing the registers
r11 and sp.
In our emulator, operands are implemented as classes in OperandAST.h.
Note: Operand classes and their implementation are provided to you; You do not need
to do anything.
Registers
While in C++ we have learned to see variables as bytes which have memory addresses,
accessing the main memory from the processor is actually slow (approximately 10
seconds). While this may sound fast, a modern processor clocks at up to 5GHz, meaning
that the same time used to access main memory can also be used to execute up to 500
instructions!
Enter the register. Registers are super-fast memory that resides on the processor itself, and
usually takes 1 clock cycle (i.e. 10 seconds) to access. This is why most instructions tend
to use registers as their operands, as they are super-fast and therefore the processor can do
more work instead of waiting for data to come around.
Most Instruction Sets these days will say how many registers the instruction set needs to
support and what size each register should be. Our emulator is loosely based on ARMv7-A,
so we will have 16 registers, each 32 bits wide.
Of note in these 16 registers:
r0 is usually used to store the return value of a function.
r13 (also known as sp) is the stack pointer; See the below section.
r14 (also known as lr) is the link register. The link register stores which instruction the
processor should jump to when this function returns.
r15 (also known as pc) is the program counter. The program counter stores the next
instruction the processor should execute.
-7
-9
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 5/15
In our emulator, you can obtain a register using u32& Processor::getRegister(u8 reg),
where reg is the register index you want to access.
Note: The implementation for the registers are provided to you.
Stack Memory
In C++ we always define variables in our function to help us implement our programs, and we
know that our variables are generally allocated on the stack. What is the stack then?
A stack data structure (as you may recall from COMP2011) is a data structure which enables
LIFO (Last In, First Out) behavior. This same principle can be applied to variables within
functions.
In assembly code, we generally use the stack as a "backing store" when there are not
enough registers to store the variables in our program.
Important: When the stack grows, the memory address decreases; When the stack
shrinks, the memory address increases. In other words, the "last-in" variables are
stored in lower (smaller) addresses, whereas "first-in" variables are stored in higher
(bigger) addresses.
In our emulator, you can access the stack memory using void MemStack::store(u32 value,
u32 addr) and u32 MemStack::load(u32 addr) to store into and load from the stack
memory respectively.
Note: The implementation for Stack Memory is provided to you; You do not need to do
anything.
Abstract Syntax Tree
An abstract syntax tree (AST) is a tree used to display the syntactical structure of a program.
The benefit of using ASTs is that the meaning of each instruction (and the entire program)
becomes unambiguous, making it easy to write what each instruction does.
In our emulator, all AST nodes are derived from the base class AST (which you will need to
write). All derived AST classes are suffixed with AST.
Library Archive
Since an emulator is very complex, we have already written some parts of the emulator for
you. These parts are distributed as a part of an archive file (.a)
An archive file is generally used when distributing libraries (i.e. a bundle of C++ functions for
external use).
Assignment Details
Since an emulator is very complex, we have already written some parts of the emulator for
you. These parts are distributed as a part of an archive file (.a). There are 15 classes you will
need to implement in 1 cpp file (around 250 lines), and 1 header file you will need to write.
Generated documentation for the header files can be found here.
Custom Integer Data Types
Unlike software, hardware often have more specific data type size requirements; For
example, the Instruction Set we are emulating explicitly states that registers must be 32 bits
wide. However, as mentioned in COMP2011, int and other data types in C++ only have a
minimum size requirement (e.g. int must be at least 16 bits). Therefore, we have provided
some custom data types which are guaranteed to have fixed sizes across all platforms.
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 6/15
These data types are defined as {s/u}{bits}, where s means its a signed integer, u means
its an unsigned integer, and bits is the number of bits of the data type (8/16/32/64 are
defined). Therefore, for example, u32 represents unsigned 32-bit integer, and s16 represents
signed 16-bit integer.
These data types are defined in Util.h.
Unsigned Overflow Behavior
While you have learned the difference between signed and unsigned integers in COMP2011,
there are other intricacies regarding these data types (which are relevant to this
assignment).
In the C++ standard, signed integer overflow is undefined behavior, meaning that there are
no guarantees what will happen. A notorious example is:
One may assume that if i is the largest number representable by int, i + 1 will overflow and
must wraparound (i.e. become 0) and thus this works to check whether an overflow may
occur. However, since this is undefined behavior, a compiler implementation may optimize it
like this:
In a mathematical point-of-view, this makes a lot of sense; In fact, recent versions of GCC
and Clang do perform this kind of optimization, as how can adding one to a value be smaller
than the value itself?
This brings us to a "special property" of unsigned arithmetic operations: overflows will
always wraparound. In other words, it is guaranteed by the standard that adding one to the
largest unsigned number of the data type will always wraparound to 0, and subtracting 1
from 0 will always wraparound to the largest unsigned number.
In addition, given a (two's-complement) signed and an unsigned number with the same bitrepresentation,
performing arithmetic operations on either number will return the same
result; In other words:
These properties may be useful when implementing addition and subtraction operations.
Source Files to Implement
Within the 15 classes, there are generally 4 types of functions to implement.
Constructor: Implement the constructor of the class.
Destructor: Implement the destructor of the class.
Accessors: Implement the accessors of the class; They may return a pointer or a
reference depending on whether the value is optional or not.
bool checkOverflow(int i) {
return i > (i + 1);
bool checkOverflow(int i) {
return false;
int lhs = 1;
int rhs = -2;
int sum = lhs + rhs;
// For demonstration purposes only; This is not technically
correct.
int uSum = static_cast(static_cast(lhs) +
static_cast(rhs));
sum == uSum;
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 7/15
eval(Processor& processor): Implement the evaluation function of the class; This
executes what is specified by the instruction on the Processor instance passed as a
reference parameter.
Note that if the dummy implementation is not provided to you in InstructionAST.cpp, the
function has been implemented for you in the library archive.
The description for each instruction is briefly described below; Refer to the header files for
more information.
nop (NopInstructionAST): Does nothing.
mov (MovInstructionAST): Copies the value of a register or constant into another
register.
mDst: The destination register to copy to.
mSrc: The source register to copy from. (Only applicable to
RegMovInstructionAST).
mImm: The 32-bit value to copy from. (Only applicable to ImmMovInstructionAST).
For example:
mov r1, r0 copies the value of register r0 into register r1.
mov r1, #1 copies the value 1 into register r1.
str (StrInstructionAST): Stores the value of a register onto the stack.
mSrc: The register whose content needs to be stored.
mDst: The memory location to store the register content to.
For example:
str r0, [r1] stores the value of register r0 into the memory address pointed to
by r1.
str r0, [r1, #8] stores the value of register r0 into the memory address
pointed to by r1, additionally offseting the memory address by 8 bytes.
ldr (LdrInstructionAST): Loads a value from the stack into a register.
mDst: The register to load to.
mSrc: The memory location to load the register content from.
For example:
ldr r0, [r1] loads into the register r0 by reading from the memory address
pointed to by r1.
ldr r0, [r1, #8] loads into the register r0 by reading from the memory address
pointed to by r1, additionally offseting the memory address by 8 bytes.
push (PushInstructionAST): Stores the values of the specified registers onto the stack,
and shifts the stack pointer.
mRegList: The list of registers whose content needs to be stored into the stack.
For example:
push {r11, sp} decrements the stack pointer by 8 bytes (2 × 32-bit registers),
then stores the value of register r11 and register sp into the memory pointed to
by sp, with r11 storing in a lower (i.e. smaller) memory address than sp.
pop (PopInstructionAST): Loads the values from the stack into the specified registers,
and shifts the stack pointer.
mRegList: The list of registers whose content needs to be loaded from the stack.
For example:
pop {r11, sp} loads the value of the register r11 and register sp by reading from
the memory address pointed to by sp, with r11 reading from a lower (i.e. smaller)
memory address, then incrementing the stack pointer by 8 bytes (2 × 32-bit
registers).
add (AddInstructionAST): Adds the value of the two operands and stores into a
register.
mDst: The destination register to store the result to.
mSrc1: The register of the first operand.
mSrc2: The register of the second operand. (Only applicable to
RegAddInstructionAST).
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 8/15
mImm: A 32-bit value as the second operand. (Only applicable to
ImmAddInstructionAST).
For example:
add r1, r1, r0 adds the value of registers r1 and r0, then stores the result in
register r1.
add r1, r1, #1 adds the value of register r1 with the constant value 1, then
stores the result in register r1.
sub (SubInstructionAST): Subtracts the value of the second operand from the first
operand and stores into a register.
mDst: The destination register to store the result to.
mSrc1: The register of the first operand.
mSrc2: The register of the second operand. (Only applicable to
RegSubInstructionAST).
mImm: A 32-bit value as the second operand. (Only applicable to
ImmSubInstructionAST).
For example:
sub r1, r1, r0 subtracts the value of register r0 from register r1, then stores
the result in register r1.
sub r1, r1, #1 subtracts the constant value 1 from the register r1, then stores
the result in register r1.
Header Files
You will also need to implement the header file for the abstract base class of AST and
ExprAST. AST is the abstract base class for all AST nodes, and ExprAST is the abstract base
class for AST nodes which represent an expression.
For AST, the class must satisify the following requirements:
There is a virtual destructor with an empty function body.
There is a const pure virtual function getTokenName which returns const char*.
There is a const pure virtual function print which accepts a parameter indent of
type u32 and returns void.
For ExprAST, the class must satisify the following requirements:
The class should inherit from AST.
There is a virtual destructor with an empty function body.
There is a const pure virtual function getTokenName which returns const char*.
There is a const virtual function print which accepts a parameter indent of type u32
and returns void.
Note that you do not need to implement ExprAST::print(u32); This has been implemented
for you as part of the library archive.
Other Implemented Classes
You can refer to the generated documentation for a description of classes already
implemented for you
Useful Implemented Functions
To aid you in your implementation, we have written the rest of the emulator for you. Below is
a list of possibly useful functions that you may need to use.
u32& Processor::getRegister(u8): Obtains a reference to the specified register.
MemStack& Processor::getStack(): Obtains a reference to the stack memory.
All public member functions in OperandAST.h
Useful Debugging Functions
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 9/15
To aid you in debugging your code, we have written some helper functions to output
additional information.
Under most circumstances, the most useful function for debugging is Processor::dump,
which outputs the current state of the processor, including instruction to-be-executed, the
current register contents, and the current stack contents. A typical output will look
something like this:
Current Instruction:
MovInstruction
|-RegisterOperand r11
|-RegisterOperand sp/r13
Registers:
r0 0x00000000 [dec:0]
r1 0x00000000 [dec:0]
r2 0x00000000 [dec:0]
r3 0x00000000 [dec:0]
r4 0x00000000 [dec:0]
r5 0x00000000 [dec:0]
r6 0x00000000 [dec:0]
r7 0x00000000 [dec:0]
r8 0x00000000 [dec:0]
r9 0x00000000 [dec:0]
r10 0x00000000 [dec:0]
r11 0x00000000 [dec:0]
r12 0x00000000 [dec:0]
sp/r13 0x7ffffff8 [offset:8]
lr/r14 0xffffffff [label: instr:0xffff]
pc/r15 0x00050002 [label:main instr:0x0002]
Stack Memory Contents:
[0:0x7fffffff~0x7ffffffc] 0xffffffff
[1:0x7ffffffb~0x7ffffff8] 0x00000000
Current Instruction: Displays the AST of the instruction to be executed.
In this example, the AST shows the equivalent of mov r11, sp.
Registers: Displays the current (hexadecimal) values of each register in the processor.
The [dec:0] field shows the decimal representation of the register values.
sp: The [offset:8] field shows the location of the stack pointer relative to the
top of the stack.
In this example, our stack pointer is currently 8 bytes below the top of the stack.
lr: The [label: instr:0xffff] fields show the name and instruction
offset of the label when "returning" from this function.
In this example, returning from this function will cause the program to jump to a
label; Jumping to a label will terminate the program,
similar to returning from main.
pc: The [label:main instr:0x0002] fields show the name and instruction offset
of the label which will be executed next. Note that label indices start from 0.
In this example, the next instruction that will be executed is 2nd instruction in the
main label.
Stack Memory Contents: Displays the current (hexadecimal) values present in the
stack. Note that this will only show values up to the current location of the stack
pointer.
The first number in the square brackets (0) shows the index of the value relative
to the top of the stack.
The hexadecimal range in the square brackets (0x7fffffff~0x7ffffffc) shows
the memory range which the value occupies.
The hexadecimal outside the square brackets shows the current value of the
memory range.
In our example, our stack would then look something like this:
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 10/15
Top of Stack
----------------
| | 0x7fffffff
| 0xffffffff |
| | 0x7ffffffc
|--------------|
| | 0x7ffffffb
| 0x00000000 |
| | 0x7ffffff8
---------------- <-- stack pointer (0x7ffffff8)
You may also set the first parameter of the constructor (Processor::Processor(bool)) to
true to enable outputting the processor state on every instruction.
There are also functions to only print the state of the memory stack (MemStack::dump) and
the AST of the current instruction (AST::print) or program (Program::printAST).
Testing Your Program
Since you are not expected to write assembly for this course, we have provided a small
number of assembly programs for your to test your program. These programs are in
main.cpp of the skeleton code.
In addition, you may also compile your own assembly programs using Compiler Explorer,
which is a tool to inspect the assembly code generated by compilers. You can type a C++
program on the left-hand side pane., and it will generate assembly on the right-hand side
pane.
Note that there are several limitations.
Only a very small subset of instructions are supported by our emulator. If you see the
message Unknown instruction in the program, that means that the specified
instruction is not supported by our emulator.
Most of the functions in the C++ standard library are not supported.
Allocating to the heap (i.e. new and delete) is not supported.
As such, we suggest writing programs which (1) do not need to include any headers, (2) only
performs stack allocation, (3) does not have any struct or class. Branch instructions have
been implemented for you, so conditional statements and loops are also supported.
This assignment uses additional C++ features and/or paradigms which are not covered in
class.
Pointer Ownership
In class, you will have learned that in most cases, a class which allocates memory from the
heap should be responsible for freeing it. This is generally referred to "pointer ownership",
where the responsibility of managing the pointer (including freeing it) lies within the class.
However, sometimes the ownership of pointers may be transferred to other classes or
variables. Take the following example:
You will notice that if we don't delete ptr in line 6, we will have created a memory leak.
However, it is impossible for createInt to free the pointer either, because there is no
mechanism for doing so!
int* createInt(int i) { return new int{i}; }

int main() {
int* ptr = createInt(2);
int ret = *ptr;
delete ptr;
return ret;
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 11/15
In this case, we say that the ownership of the heap-allocated memory (referenced by the
variable ptr) is transferred to the caller of createInt, meaning that it is now the
responsibility of the main function to call delete on ptr.
In our assignment, all constructor parameters will have their ownership transferred to your
class, meaning that you will need to call delete on these pointers on destruction.
Pointer-to-Implementation
Throughout the provided classes, you will see a common coding pattern similar to this:
This paradigm is called "pointer to implementation" (or pImpl for short). The benefit of using
this paradigm is that since the real implementation (including data members) are hidden in a
cpp file, you do not need to change the class definition in the header file when adding,
changing, or removing members, meaning that there is no need to recompile files which
depend on the class definition.
You can read more about this here.
Anonymous Namespaces
Anonymous namespaces have the same effect as declaring static for a global variable: It
restricts the variable to have internal linkage, i.e. the variable is only visible within the same
C++ source file.
For more details, please refer to the self-study notes.
Raw String Literals
In the provided main.cpp and AssemblyProgram.cpp, you will see some usages of R"()".
This syntax (introduced in C++11) is called a "raw string literal", and it is used when you want
to treat the entire string as-is. As an example:
This is very convenient when you want to copy-and-paste a multiline string without adding
\n yourself.
Function Pointer
In AssemblyProgram.cpp, you will see a function with the following prototype:
bool testProgram(
const std::string& input,
void(* setup)(Processor&),
u32 expected,
u32(* actualExpr)(const Processor&),
const std::string& cmpValueName
);
The syntax for the second and forth parameters will look foreign to most of you. To explain
what they are, let us revisit lambdas from the beginning of the semester...
Back then, the instructors used auto to let the compiler deduce the type of lambdas. This is
because the types of lambdas are what is known as a closure type. Closure types are
internally defined by the C++ standard, and each lambda you define will have a unique
class SomeClass {
public:
// public member functions...
private:
struct Impl;
Impl* mImpl;
const char* stringLiteral = "abc\ndef";
// Can be written as:
const char* rawStringLiteral= R"(abc
def)";
2021/3/21 COMP 2012 Assignment 2: Assembly Emulator
https://course.cse.ust.hk/comp2012/assignments/assignment2/ 12/15
unnamed closure type. Since this closure type is unnamed, there is no way of declaring the
type of a lambda variable, and therefore the common practice is to use auto to let the
compiler determine the type.
Function pointers, as their name suggests, are pointers to functions. Before lambdas were
introduced in C++11, function pointers are used to select which function to call at runtime
(without the need for classes and inheritance) as well as higher-order functions. The
following example demonstrates runtime selection of functions:
The function pointer parameter can be seen in the parameter double (*fn)(double). The
name of the parameter is fn, the pointer is pointing to a function of type double(*)(double),
i.e. a function which has one parameter (double) and returns a double. By explicitly
specifying the type of function we want in operateOnDouble, we can make sure that callers
pass in a function (or lambda) with the correct number of parameters and return type.
Some of you may notice that we added + to the lambda variable before passing into the
function pointer parameter. This is a way to cast a lambda into a function pointer.
However, note that only lambdas with no captures can be cast to a function pointer.
Note that there is a class in the Standard Template Library which allows capturing all kinds of
lambdas and function pointers (std::function), but this will not be covered in this
description or this course.
double operateOnDouble(double (*fn)(double), double d) {
return fn(d);
}
double timesTwo(double d) {
return d * 2;
}
int main() {
auto square = [](double d) { return d * d; };

operateOnDouble(×Two, 3); // 6
operateOnDouble(+square, 3); // 9
Download
Skeleton code: skeleton.zip
Note that you should only change and submit AST.h and InstructionAST.cpp. You do not
need to understand the implementation of the provided C++ source files.
This is a Makefile project. Just do what you did in lab 1 to put that in your VS Code / Eclipse /
other IDEs. In your terminal, running make will create a single executable pa2.exe. See the
Sample Output and Grading Scheme section for more information about the test cases.
Note that all header files are provided in the

联系我们
  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-21:00
  • 微信:codinghelp
热点标签

联系我们 - QQ: 99515681 微信:codinghelp
程序辅导网!