Overview
We discuss C++ value passing and returning, the sizes of objects, and recap some information on segments.
Modifying an argument
def f(arg):
arg = -1
x = 2
f(x)
print("x is {}".format(x))
- We pass an integer variable,
x
, as an argument to a function - The function modifies its parameter
- Does that modify the caller’s value?
- No!
- The function parameter is a copy of the caller’s value
- This is called call-by-value
Modifying a complex argument
def f(arg):
arg = [-1]
l = [2]
f(l)
print("l[0] is {}".format(l[0]))
- Does modifying
arg
affect the caller? - Again no!
Modifying a complex argument’s value
def f(arg):
arg[0] = -1
l = [2]
f(l)
print("l[0] is {}".format(l[0]))
- Does modifying
arg
’s value affect the caller? - YES!
- Python is not actually a call-by-value language
- It is a call-by-object language (like Javascript, Java, …)
- Simple objects are passed by value (int, bool, …)
- Complex objects are effectively passed by pointer
- Modifications to the parameter are not visible in the caller
- But modifications to the parameter’s value are visible
C++
- C++ is a call-by-value language
C++ modifying an argument
void f(int arg) {
arg = -1;
}
int main() {
int x = 2;
f(x);
printf("x is %d\n", x);
}
C++ modifying a complex argument
void f(std::vector<int> arg) {
arg = {-1};
}
int main() {
std::vector<int> l = {2};
f(l);
printf("l[0] is %d\n", l[0]);
}
C++ modifying a complex argument’s value
void f(std::vector<int> arg) {
arg[0] = -1;
}
int main() {
std::vector<int> l = {2};
f(l);
printf("l[0] is %d\n", l[0]);
}
- The caller’s value does not change!
- C++ arguments are passed by value regardless of type
- This can require copying an enormous data structure!
- Try making
l
bigger and turning off optimization to see the effect
Call-by-reference
- To avoid copying a data structure, pass it by reference using an explicit reference type
void f(std::vector<int>& arg) { // `&` means reference
arg[0] = -1;
}
int main() {
std::vector<int> l = {2};
f(l);
printf("l[0] is %d\n", l[0]);
}
- Now all modifications to the parameter are visible to the caller
- The parameter
arg
becomes an alias (a different name) for the caller’s variablel
Every declared object has constant size
- The compiler needs to know the size of every object in order to lay it out in memory
- Every declared variable has constant size
- For instance, array bounds must be constant
- Can’t say
int n = ...; int a[n];
- Only dynamic allocation can create dynamically-sized arrays
How does std::vector
work?
- What is
sizeof(std::vector<int>)
?
Array parameters and return values are special
- C++ array parameters and return values are not passed by value
- They are passed as pointers
void f(int arg[]) {
arg[0] = -1;
}
int main() {
int l[2] = {2, 0};
f(l);
printf("l[0] is %d\n", l[0]);
}
- This is surprising!
- We do not recommend passing normal arrays as arguments or return values
- Pass pointers instead
- Maybe C++ is bad (it is)
- But everything in computers is weird until you get used to it
Stack segment growth
int f1(int f1arg) {
hexdump_named_object(f1arg);
return f1arg;
}
int f2(int f2arg) {
hexdump_named_object(f2arg);
return f1(rand()) + f2arg;
}
...
int f5(int f5arg) {
hexdump_named_object(f5arg);
return f4(rand()) + f5arg;
}
f5
callsf4
, which callsf3
, which callsf2
, which callsf1
Recursive call
- A recursive function is a function that can call itself
int f(int x) {
int r = 0;
...
if (x > 0) {
r += f(x - 1);
}
...
return r;
}
int main() {
f(100);
}
Stack layout vs. struct layout
- Struct layout is fixed by the abstract machine, to facilitate
interoperability with hardware and between different compilers
- If you want a tighter layout, you’ve got to do it yourself
- Local variable layout has no such constraint!