int i = 0; while(A[i] != x && i < n) i++; if (i == n) cout << "Not found!"; else cout << "Found!"; cout << endl;You were supposed to recognize that, due to how short-circuit evaluation of && works, you would be testing A[i] before recognizing that i was actually too big to use as an index for the array A. However, if you compile and run this code, we may not see any problem. In fact, it's pretty likely the program will function exactly the way you want. So how can I say there's and "error"? Well, accessing an array at indices outside of its range is an error ... it's just kind of hard to predict when or whether that error will manifest itself in bad behavior that the user sees. In fact, we've seen this same kind of thing with unitialized variable errors. We get programs that sometimes do run OK, and sometimes don't.
There's an amazing tool called valgrind that we
can use to monitor the execution of our program to detect
whether there are array out of bound accesses, as well as
other bad things like memory leaks. Here's an example:
Note: the new syntax
new int[3]{42,13,88}
that that both allocates an array of 3 ints
and initializes them to 42, 13 and 88. I'm
just showing it here because it's so darn
useful! |
$ g++ -o bad badexample.cpp $ valgrind ./bad ==6671== Memcheck, a memory error detector ==6671== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==6671== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info ==6671== Command: ./bad ==6671== ==6671== Invalid read of size 4 ==6671== at 0x108969: main (in /home/wcbrown/courses/S20SI204/S20SI204/lec/l22/bad) ==6671== Address 0x5b7dc8c is 0 bytes after a block of size 12 alloc'd ==6671== at 0x4C3089F: operator new[](unsigned long) (in ...) ==6671== by 0x108922: main (in /home/wcbrown/courses/S20SI204/S20SI204/lec/l22/bad) ==6671== Not found! ==6671== ==6671== HEAP SUMMARY: ==6671== in use at exit: 12 bytes in 1 blocks ==6671== total heap usage: 3 allocs, 2 frees, 73,740 bytes allocated ==6671== ==6671== LEAK SUMMARY: ==6671== definitely lost: 12 bytes in 1 blocks ==6671== indirectly lost: 0 bytes in 0 blocks ==6671== possibly lost: 0 bytes in 0 blocks ==6671== still reachable: 0 bytes in 0 blocks ==6671== suppressed: 0 bytes in 0 blocks ==6671== Rerun with --leak-check=full to see details of leaked memory ==6671== ==6671== For counts of detected and suppressed errors, rerun with: -v ==6671== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) |
See how valgrind is telling us that "Invalid read of size 4"? That's because we are reading one int (4 byte) past the end of our array. Valgrind can give you more information if you compile with the -g option tp g++, which puts "debugging" information into the executable. Specifically, it inserts information that connects the machine code in the executable with the line of source code that produced it.
g++ -g -o bad badexample.cppIn this case valgrind's error message includes
==7204== Invalid read of size 4 ==7204== at 0x108969: main (badexample.cpp:15)which shows us that line 15 of badexample.cpp is where the invalid read occurred. Also notice that valgrind is reporting that we have a "memory leak". That's because we did a
delete[] on our array to free that memory. So
the end of main needs the line:
delete [] A;
We'll look at this stuff first from the prespective of our simple program from last class for computing averages. Here's that program. We're going to try and break it up into separate componants using functions. Essentially we'd like the bulk of the program to consist of these two function calls:
// Create array A and read N int's from input int *A = readints(N,fin); // Compute average double average = sum(A,N) / double(N);In defining these two functions, we'll see how arrays get passed in and out of functions.
int* readints(int N, istream &IN);. This
function should return an array of N
ints, populated with values read in from
IN. The basics of the function should be fairly
straightforward. The important thing is to understand what happens!
So, here's the function definition:
int* readints(int N, istream &IN)
{
// Create the array
int* B = new int[N];
// Read values into the array
for(int i = 0; i < N; i++)
IN >> B[i];
// Return pointer to array
return B;
}
And here's a pictorial depiction of what goes on. Notice that what
gets returned from the function is a point, not an actual
array. The array of ints does not disappear
when the function call returns, because memory allocated with
new has no scope or lifetime ... it just sticks
around until the end of the program, or until it's explicitly
destroyed with a call to delete, which we'll discuss
later.
![]() |
![]() |
![]() |
![]() |
![]() |
new
and return them by returning a pointer to the block of memory
allocated by the call to new.
So, getting back to our example, we need to implement the
function int sum(int* A, int N);. Notice that when
we pass an array to a function we always have to pass the length
of the array along with it, otherwise we have no idea how many
elements are in the array pointed to by A.
Hopefully the definition of this function looks straightforward.
int sum(int* A, int N)
{
int total = 0;
for(int i = 0; i < N; i++)
total = total + A[i];
return total;
}
The question is this: What really goes on when we run it?
Hopefully the pictures below, which illustrate what goes on in a
sample run of the above function, will make clear to you what
really goes on when arrays are passed to functions.
![]() |
![]() |
![]() |
![]() |
![]() |
Here's a good example of where we might want to do this. Continuing with the example program we've been adding functions to, what if these numbers were grades, and I wanted to add a curve to these grades? I might imagine having a function "bump" that would take an array and an integer "delta", and modify the array by increasing every value in the array by delta. So at the end of main() I might make the call
bump(A,N,int(80 - average + 0.5));... and then print out the contents of the array after the bump. Can you define the "bump" function?
contains predicate.
Write function
bool contains(string *A, int N, string s);
that tells you whether or not the string s is
contained in the array A.
getarray function.
Write a function
int* getarray(int N, int x);
that allocates and returns a pointer to an array of
N ints, each initialized to the
value x.
shiftleft function.
Write a function
double shiftleft(double *A, int N, double x);
that shifts all the elements in array A to the left by 1
position, puts the value x in the rightmost
position in the array, and returns the value that had
previously been in the leftmost position.
average function and the
shiftleft function to do the smoothing problem
from the last lab. Here's my solution.