Dynamic memory allocation has undesired output [duplicate] - c++

This question already has answers here:
Operator new initializes memory to zero
(4 answers)
Closed 8 years ago.
So I started learning C++ last week and naturally, I want to become familiar with the whole pointer and object-oriented business and so on and so forth.
To do that, I'm writing a very simple program for some basic matrix calculations:
# include <iostream>
using std::cout;
using std::cin;
class Matrix {
int columns; // x
int rows; // y
double* matrix;
public:
Matrix (int*);
void printMatrix();
void free() {
delete[] matrix;
return;
};
};
Matrix::Matrix(int* dim){
rows = *dim;
columns = *(dim + 1);
matrix = new double [columns*rows];
}
void Matrix::printMatrix(){
int i, j;
for(i = 0; i < columns; i++){
for(j=0; j < rows; j++){
cout << matrix[columns*i + j] << " ";
}
cout << "\n";
}
return;
}
int* getMatrix ();
int main () {
Matrix matrix (getMatrix());
matrix.printMatrix();
matrix.free();
return 0;
}
int* getMatrix (){
int* dim = new int [2];
cout << "(m x n)-Matrix, m? ";
cin >> dim[0];
cout << "n? ";
cin >> dim[1];
return dim;
}
The problem (as I see it) occurs when I choose a (4,2) matrix. As I understand from various tutorials,
matrix = new double [columns*rows];
should allocate this much memory: columns*rows times sizeof(double). Also, every 'cell' should be initialized with a 0.
But, choosing a (4,2) matrix, I get the following output, of the function printMatrix():
0 0
0 0
0 6.6727e-319
0 0
Why is the (3,2) entry not initialized with 0?
Thanks!

Also, every 'cell' should be initialized with a 0.
Nope. The language does not do that for you, when you write new double[N].
Why is the (3,2) entry not initialized with 0?
It will if, you write new double[N]() instead!
[C++11: 5.3.4/15]: A new-expression that creates an object of type T initializes that object as follows:
If the new-initializer is omitted, the object is default-initialized (8.5); if no initialization is performed, the object has indeterminate value.
Otherwise, the new-initializer is interpreted according to the initialization rules of 8.5 for direct-initialization.
Granted, this is slightly ambiguous in that it would seem to be talking about the non-array versions of new, but in fact it means both; T is double[4].
In fact, we can see that the same section of wording talks about "object" in both the array and non-array cases, setting the perfect precedent:
[C++11: 5.3.4/1]: [..] If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression
returns a pointer to the initial element of the array.
Now, it's essentially impossible to prove this rule, because you can strike unlucky and get all-zeroes even when those values are in fact indeterminate, but the following code entirely unconvincingly makes a good start:
#include <iostream>
#include <vector>
int main() {
const std::size_t n = 4;
{
std::vector<double> hack;
hack.push_back(5);
hack.push_back(6);
hack.push_back(7);
hack.push_back(8);
hack.push_back(9);
hack.push_back(10);
hack.push_back(11);
hack.push_back(12);
}
double* a = new double [n];
double* b = new double [n]();
for (std::size_t i = 0; i < n; i++)
std::cout << a[i] << '/' << b[i] << ' ';
std::cout << '\n';
delete[] a;
delete[] b;
}
I managed to get 0/0 6/0 7/0 8/0 from it, thanks to some heap hackery, but it's still only just pure chance and doesn't really demonstrate anything (live demo).
Unfortunately, new double[4](316) isn't valid (providing a value inside the () is explicitly banned for arrays during direct-initialization, per [C++11: 8.5/16] ) so we can't suggest that new double[4](0) would be reliable and use the example with 316 to convince you of it.

Only static variables are initialized to 0 in C++.
Auto and dynamic variables should be initialized by you.

Related

Why does passing a 2D array without the complete size of the array still compile and run? [duplicate]

This question already has answers here:
How do I define variable of type int[][26]?
(2 answers)
Closed 2 years ago.
I have written a small program that passes a 2D array to 2 separate functions that store and display the pattern of a chessboard. While the program works perfectly fine, I would like to ask a more technical question that I have not been able to answer myself through my searches.
I am wondering how it is possible that my program compiles and runs when passing the 2D array with ONLY the columns variable specified, but not the rows. For instance void setBoard(char chessboard[][cols]);
Here is the link to the program: https://codecatch.net/post/54969994-76d7-414b-aab6-997d3fef895c
Here is the same code for those of you that don't want to click the link:
#include<iostream>
using namespace std;
const int rows = 8;
const int cols = 8;
char chessboard[rows][cols];
void setBoard(char chessboard[][cols]);
void printBoard(char chessboard[][cols]);
void setBoard(char chessboard[][cols]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(i % 2 == 0 && j % 2 == 0) {
chessboard[i][j] = 'x';
} else {
if(i % 2 != 0 && j % 2 == 1) {
chessboard[i][j] = 'x';
} else {
chessboard[i][j] = '-';
}
}
}
}
return;
}
void printBoard(char chessboard[][cols]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
cout << chessboard[i][j] << " ";
}
cout << endl;
}
return;
}
int main(int argc, char const *argv[])
{
setBoard(chessboard);
printBoard(chessboard);
return 0;
}
To answer the question directly: in C and C++ passing static array as an argument needs all dimensions except the outermost. So, if you have N-dimensional array, you need to specify the sizes of N-1 dimensions, except the leftmost one: int array[][4][7][2]...[3].
Now, to the gory details.
So, let's say you have int a[3];. What is the type of a? It is int [3]. Now, you want to pass it into function. How do you do this? There are two ways: you can either pass the array by pointer to the first element (works in C and C++), or you can pass the array by reference (references are a C++ thing). Let's consider these examples:
#include <iostream>
void foo(int* array, std::size_t len) { // same as foo(int array[],...)
for (std::size_t i = 0; i < len; i++)
std::cout << array[i] << ' ';
std::cout << '\n';
}
void bar(int (&array)[3]) {
for (int n : array)
std::cout << n << ' ';
std::cout << '\n';
}
template <std::size_t N>
void baz(int (&array)[N]) {
for (int n : array)
std::cout << n << ' ';
std::cout << '\n';
}
int main () {
int a[3] = {1, 2, 3};
int b[4] = {1, 2, 3, 4};
foo(a, 3); // BAD: works, but have to specify size manually
foo(b, 4); // BAD: works, but have to specify size manually
bar(a); // BAD: works, but bar() only accepts arrays of size 3
bar(b); // DOESN'T COMPILE: bar() only accepts arrays of size 3
baz(a); // OK: size is part of type, baz() accepts any size
baz(b); // OK: size is part of type, baz() accepts any size
}
Let's consider foo().
foo()'s signature could also be written as void foo(int array[], ...). This is purely syntactic sugar and int array[] means the same as int* array. Note, however, that this only applies to function signatures, everywhere else these syntaxes are not equivalent.
When you call it as foo(a, 3), it's signature is set to accept a pointer to int as first parameter: int* array. But you know that a is of type int [3], so how does it work? What happens is, the pointer to the first element of the array is passed by value. Which means, it is the same as writing foo(&a[0],...). We take the address of the first element, which is then copied into int* array. As you might notice, having a pointer to the first array doesn't tell us anything about the size of the array, we lost this information during conversion from array type int [3] to int *. Which is why we have to supply the second argument that specifies the length of a. We call this implicit conversion an "array to pointer decay". Decay specifically because we were forced to lose important information -- we had it right there in the type, but now we have to have another argument that describes how many elements the array has. A bit stupid and inconvenient, isn't it?
Now consider bar().
In bar() we pass the array type by reference. This is something that C++ has improved upon C. I will not explain what references are, but in general, you can think of them as something that allows you to get the object the way it is defined, without using any convertions to pointers. In this case, the type of array remains int [3], so we have passed in an array and haven't lost any type information. Great! This means, we can use idiomatic C++ syntax to further improve our code. I have replaced the normal for loop, as found in foo(), with a for-each loop, where we just need to supply a variable to store the element (n) and the array (array). Note that this is possible only because array preserves type information! Trying to do this in foo() would result in a compilation error.
However, there is still a problem with this. bar() has to have the array size as part of its signature! This means that if a was any different size, let's say 4 elements, trying to use bar() would result in a compilation error because int [3] and int [4] are incompatible types.
Consider baz(), which solves the above problem.
Just a little bit of templates will make baz() usable on arrays of any size, while the usage stays the same as in bar().
Now, let's take it to multiple dimensions:
#include <iostream>
void foo2d(int (*array)[3], std::size_t rows, std::size_t cols) {
for (std::size_t i = 0; i < rows; i++)
for (std::size_t j = 0; j < cols; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
void bar2d(int (&array)[2][3]) {
for (std::size_t i = 0; i < 2; i++)
for (std::size_t j = 0; j < 3; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
template <std::size_t N, std::size_t M>
void baz2d(int (&array)[N][M]) {
for (std::size_t i = 0; i < N; i++)
for (std::size_t j = 0; j < M; j++)
std::cout << array[i][j] << ' ';
std::cout << '\n';
}
int main () {
int c[2][3] = { {1, 2, 3}, {4, 5, 6} };
foo2d(c, 2, 3);
bar2d(c);
baz2d(c);
}
And again, only baz2d() doesn't require hardcoded size information.
One more example, of foo3d(), just to demonstrate what I mean when I say only the outermost dimension doesn't need to be specified:
void foo3d(int (*array)[2][1], std::size_t rows, std::size_t cols, std::size_t K) {
for (std::size_t i = 0; i < rows; i++)
for (std::size_t j = 0; j < cols; j++)
for (std::size_t k = 0; k < K; k++)
std::cout << array[i][j][k] << ' ';
std::cout << '\n';
}
int main () {
int d[3][2][1] = { {{1}, {2}}, {{3}, {4}}, {{5}, {6}} };
foo3d(d, 3, 2, 1);
}
Pay attention to how it's called vs. how it's declared in the signature. So, why do you not need to declare the outermost size? Because only the first pointer decays, due to passing it to the function. d[0][0][0] stores element of type int, d[0][0] stores element of type int [1], d[0] stores element of type int [2][1]. Which are all themselves are arrays! Well, except d[0][0][0], obviously. So, what is the type of the array in foo3d()? It's int (*)[2][1]: pointer to array of size 2, each element of which is an array of size 1.
Parameter of a function is never an array type in C++ (nor in C).
You can declare a function that has an array parameter (as is done in the example program), but that declaration is adjusted so that the parameter is not an array as was written, but it is instead a pointer to element of such array. Example:
void f(int[]); // will be adjusted
void f(int[42]); // will be adjusted
void f(int*); // declarations above are adjusted to this
All of the above three declarations declare the same function. If the type of parameter is "array of int", then the type of the element of such array is "int" and pointer to such element is "pointer to int". Notice that the size of the array has no effect on the type of the element. As such, the size has no effect on the declaration in any way, and indeed arrays of unknown bound are allowed in parameter declaration.
Note that this adjustment occurs only in function parameters, and nowhere else. An array is not a pointer.
So, when you declare a function void setBoard(char chessboard[][cols], the parameter chessboard is not an array, because a parameter is never an array. It has been adjusted to be a pointer to element of char [][cols]. Element of such array is char[cols] i.e. array of cols number of char, therefore the adjustead parameter type is pointer to array of cols number of char i.e. char(*)[cols].
You cannot have pointer type to an array of unknown bound, so you cannot leave out cols. But you can leave out rows because as noted above, the size of the declared array parameter is ignored when the type is adjusted to be a pointer to the element of that array.
You may be wondering "if the parameter is actually not an array, then why can an array be passed as argument?". The answer is that there is another rule complementing the parameter adjustment (in simple words): Arrays implicitly convert to the pointer to element type. The result of the conversion is pointer to the first element of that array. Such conversion is called "decaying". This conversion happens automatically whenever the value of an array is used. Example:
printBoard(&chessboard[0]);
printBoard(chessboard);
The above function calls do exactly the same thing. The former explicitly passes pointer to first element, while the latter does the same thing by implicit "decay".

How to check if certain position in array is modified using pointers

I don't understand how I can check if a certain position in an array is been modified or not. Below is an example:
int array[5];
array[2] = 23;
array[4] = 23;
for (int i = 0; i < 5; ++i) {
if (array[i] == ????){
cout << "in array";
} else {
cout << "not in array";
}
}
So I wanted to know how would I get it so the if statement is checking if the item has been modified. So once I becomes 2 it will says it's in array and if not the it should print 'not in array'.
This has to be done using pointers.
This is undefined behavior because array[0] isn't initialized. So when you compare it in your if, what will happen?
You could initialize them all to a value that you consider as "not modified", and check for this value. For instance:
int array[5] = {}; // initializes all elements to the default value for int, which is 0
And then, in your if:
if (array[i] != 0 ) {
If you can't do that because you need the full range of int values, then you can use std::optional instead:
#include <iostream>
#include <optional>
int main() {
std::optional<int> array[5];
array[2] = 23;
array[4] = 0;
for (int i = 0; i < 5; ++i) {
if (array[i]) {
std::cout << "in array" << std::endl;
}
else { std::cout << "not in array" << std::endl; }
}
}
You can't. In C++, it's impossible to determine if an object is uninitialized. Any attempt to read the value of an uninitialized object is Undefined Behavior. They're effectively write-only.
(You might also have a problem with the terminology, or a lack of understanding. array[0] is in the array from the very start, it's just not yet initialized. )
You might use std::map<int, int> values instead. It can truly be empty (values.empty()==true) When you write values[2]=0, a new value is added, and values.size() will be 1 to reflect the new number of elements.
I don't understand how I can check if a certain position in an array is been modified or not.
On x86 you can set a hardware breakpoint on read/write/execute access to a value at a specific address of length of up to 8 bytes. On Linux one API for that is perf_event_open with PERF_TYPE_BREAKPOINT event type. The value of the event counter is how many of interesting accesses to the value at the address have been made.
One option would be using two arrays. A bool array to store the initialization state, and another array to keep desired objects.
(However std::array or std::vector would be better choices than using normal arrays, but that's a different concern)
E.g.
constexpr unsigned int ArrayLen = 5;
bool isInitialized[ArrayLen] = {false};
MyType myArray[ArrayLen]; // In your case `MyType = int`
...
// When setting/resetting update both arrays
void Set(int i, MyType obj)
{
assert(i < ArrayLen);
isInitialized[i] = true;
myArray[i] = std::move(obj);
}
...
// check isInitialized array first before accessing an element
unsigned int targetIndex = 2;
if (isInitialized[targetIndex])
{
auto& obj = myArray[targetIndex];
// use `obj` ...
} else {
// object in `targetIndex` is not initialized
}
In C++17 you can use std::optional so that the initialization state doesn't need to be separately maintained but instead both the state and the object will be bound to a single std::optional object.

Why do we really need pointers in Programming specifically C++

I can't really understand the difference between Dynamic and static allocation,they say Dynamic allocation happens while executing the program and static only while compiling and we can't allocate manually while execution but,
#include <iostream>
using namespace std;
int main()
{
int size , a = 0;
cout << "Enter the size of Array: ";
cin >> size;
int A[size][size];
for(int i = 0 ; i < size ; i++)
{
for(int j = 0 ; j < size ; j++)
cout << a++ << '\t';
cout << endl;
}
system("pause");
return 0;
}
This program will allocate the Array size while execution.
The real point of dynamic allocation is that you control the lifetime of the objects being allocated. Dynamically allocated objects exist until you deallocate them. It's not really anything to do with arrays, although that is often the context in which beginners are first taught about allocation.
Consider these two functions
int* bad()
{
int x = 123;
return &x;
}
int* good()
{
int* x = new int(123);
return x;
}
Both functions create an int and return a pointer to that int.
The bad function is incorrect because the x variable is destroyed when the function exits, so it returns a pointer to an object which has been destroyed.
The good function creates an int dynamically, that object will never be destroyed (unless the program deletes it). So this function is correct.
Incidentally int size; ... int A[size][size]; is not legal C++. Some compilers allow it, but other compilers would not.

C++: The value that the pointer is pointing to changes

I am trying to code a class that represents a set of integers. It's a homework assignment but for the life of me I cannot figure out this issue.
In the class "IntSet", I have two private variables; one is a pointer to an array the other is the size of the array. I can create objects of this class and they work as intended. But I have this function named "join" that returns an object of the IntSet class. It essentially concatenates the arrays together then uses that array to create the returning object.
Here is my code:
#include <iostream>
using namespace std;
class IntSet {
int * arrPtr;
int arrSize;
public:
//Default Constructor
IntSet() {
int arr[0];
arrPtr = arr;
arrSize = 0;
}
//Overloaded Constructor
IntSet(int arr[], int size) {
arrPtr = arr;
arrSize = size;
}
//Copy Constructor
IntSet(const IntSet &i) {
arrPtr = i.arrPtr;
arrSize = i.arrSize;
}
/*
* Returns a pointer to the first
* element in the array
*/
int* getArr() {
return arrPtr;
}
int getSize() {
return arrSize;
}
IntSet join(IntSet &setAdd) {
//Make a new array
int temp[arrSize + setAdd.getSize()];
//Add the the values from the current instance's array pointer
//to the beginning of the temp array
for (int i = 0; i < arrSize; i++) {
temp[i] = *(arrPtr + i);
}
//Add the values from the passed in object's array pointer
//to the temp array but after the previously added values
for (int i = 0; i < setAdd.getSize(); i++) {
temp[i + arrSize] = *(setAdd.getArr() + i);
}
//Create a new instance that takes the temp array pointer and the
//size of the temp array
IntSet i(temp, arrSize + setAdd.getSize());
//Showing that the instance before it passes works as expected
cout << "In join function:" << endl;
for (int j = 0; j < i.getSize(); j++) {
cout << *(i.getArr() + j) << endl;
}
//Return the object
return i;
}
};
int main() {
//Make two arrays
int arr1[2] = {2 ,4};
int arr2[3] = {5, 2, 7};
//Make two objects normally
IntSet i(arr1, 2);
IntSet j(arr2, 3);
//This object has an "array" that has arr1 and arr2 concatenated, essentially
//I use the copy constructor here but the issue still occurs if I instead use
//Inset k = i.join(j);
IntSet k(i.join(j));
//Shows the error. It is not the same values as it was before it was returned
cout << "In main function:" << endl;
for (int l = 0; l < k.getSize(); l++) {
cout << *(k.getArr() + l) << endl;
}
return 0;
}
The program compiles and the output as of now is:
In join function:
2
4
5
2
7
In main function:
10
0
-2020743083
32737
-2017308032
I don't know why but the 10 and 0 are always the same every time I recompile and run. Also, if I print out the address of the pointer rather than the value(in both the join function and the main function), I get the same memory address.
Sorry if I misuse terms, I come from a java background, so pointers and such are a little new to me. If any clarification is needed, please ask.
Thanks in advance.
int temp[arrSize + setAdd.getSize()];
This is a local array, its lifetime ends once the function returned.
IntSet i(temp, arrSize + setAdd.getSize());
Here you are constructing an IntSet with this array. In fact the constructor simply changes a member pointer to the value of temp:
IntSet(int arr[], int size) {
arrPtr = arr;
arrSize = size;
}
As a result, since the lifetime of the object that temp and consequently also i.arrPtr is pointing to ends after leaving join, you will have a wild pointer. Dereferencing this pointer later in main invokes undefined behavior.
You need to allocate the array dynamically with new[] and delete it later with delete[]. The same goes for your constructors. Also note that if you use new[] in join and delete[] in the destructor, then you also have to make sure that the copy constructor actually copies the array (create new array with new[] and copy contents). If you simply assign the pointer then both the source and destination object will point to the same array and they will also both try to delete it at deconstruction, again invoking undefined behaviour.
But since this C++, you might as well use a std::vector which does all of this for you. (or std::set if you actually want a integer set)
The quickest fix with your code is to change
int temp[arrSize + setAdd.getSize()];
into this
int * temp = new int[arrSize + setAdd.getSize()];
The thing is that you allocated temp on the stack, so when join() returns that memory is releases. By allocating memory on the heap (as per the fix) the memory is not released when join() returns.
There are other things wrong with your code -- depending on the point of the assignment. I think most of these will be fixed when you consider the implications of having memory on the heap.

Segmentation fault and mysterious loop behavior

I am working on a homework assignment with a few specific requirements. There must be a class named TestScores that takes an array of scores as its argument. It throws an exception if any scores are negative or greater than 100. Finally, it must have a member function that returns an average for all the scores. I wasn't clever enough to find a way to only pass the array into the constructor, so I also added in an int that tells the size of the array.
Running the code (I haven't even gotten around to testing the exceptions yet), I keep getting a Segmentation fault error. Valgrind and gdb have been rather unhelpful, outputting messages like:
==9765== Jump to the invalid address stated on the next line
==9765== at 0x2200000017: ???
Even more mysteriously (to me at least), in the for loop in the client code, my incrementor, i, somehow gets bumped from 0 to a seemingly random two-digit number right after creating the TestScores object. In previous versions, before I started using rand() to populate the array, i just never incremented and did the infinite loop thing.
Here's the contents of TestScores.cpp:
#include <iostream>
using std::cout;
using std::endl;
#include "TestScores.h"
#include <stdexcept>
using std::runtime_error;
// Constructor.
TestScores::TestScores(int a[], int s):
_SIZE(s), _scores()
{
// Look at each item in a[], see if any of them are invalid numbers, and
// only if the number is ok do we populate _scores[] with the value.
for (int i = 0; i < _SIZE; ++i)
{
if (a[i] < 0)
{
throw runtime_error ("Negative Score");
}
else if (a[i] > 100)
{
throw runtime_error ("Excessive Score");
}
_scores[i] = a[i];
cout << _scores[i] << " ";
}
cout << endl;
}
// Finds the arithmetic mean of all the scores, using _size as the number of
// scores.
double TestScores::mean()
{
double total = 0;
for (int i = 0; i < _SIZE; ++i)
{
total += _scores[i];
}
return total / _SIZE;
}
// median() creates an array that orderes the test scores by value and then
// locates the middle value.
double TestScores::median()
{
// Copy the array so we can sort it while preserving the original.
int a[_SIZE];
for (int i = 0; i < _SIZE; ++i)
{
a[i] = _scores[i];
}
// Sort the array using selection sort.
for (int i = 0; i < _SIZE; ++i)
{
int min = a[i];
for (int j = i + 1; j < _SIZE; ++j)
{
if (a[j] < min)
{
min = a[j];
a[j] = a[i];
a[i] = min;
}
}
}
// Now that array is ordered, just pick one of the middle values.
return a[_SIZE / 2];
}
And here's the client code:
#include <iostream>
#include "TestScores.h"
#include <stdexcept>
#include <cstdlib>
#include <ctime>
using std::exception;
using std::cout;
using std::endl;
int main()
{
const int NUM_STUDENTS = 20,
NUM_TESTS = 4;
int test [NUM_TESTS][NUM_STUDENTS];
// Make random seed to populate the arrays with data.
unsigned seed = time(0);
srand(seed);
// Populate the scores for the individual tests graded for the semester.
// These will all be values between 0 and 100.
for (int i = 0; i < NUM_TESTS; ++i)
{
for (int j = 0; j < NUM_STUDENTS; ++j)
{
test[i][j] = rand() % 100;
cout << test[i][j] << " ";
}
cout << endl;
}
// Now we have the data, find the mean and median results for each test.
// All values should be valid, but we'll handle exceptions here.
for (int i = 0; i < NUM_TESTS; ++i)
{
cout << "For Test #" << i + 1 << endl;
try
{
cout << "i = " << i << endl; // i = 0 here.
TestScores results(test[i], NUM_STUDENTS);
cout << "i = " << i << endl; // i = some random number here.
cout << "Mean: " << results.mean() << endl;
cout << "Median:" << results.median() << endl << endl;
}
catch (exception &e)
{
cout << "Error, invalid score: " << e.what() << endl;
}
cout << "For Test #" << i + 1 << endl;
}
return 0;
}
Edit:
The header was requested as well:
#ifndef TEST_SCORES_H
#define TEST_SCORES_H
class TestScores
{
private:
const int _SIZE;
int _scores[];
public:
// Constructor
TestScores(int a[], int);
double mean() const,
median() const;
};
#endif
I played around with making the array dynamic, and didn't initialize the array as empty, which fixed my problems, so that's what I ended up turning in. That leads me to a few follow-up questions.
Before going dynamic, I played around with initializing the array, _scores, by trying to give it the size value that was supposed to already be initialized. This led to compiler problems. I talked with my teacher about that, and he said that you can't allocate space for an array unless there's a hardwired global constant. That is, you can't pass a size value in the constructor to initialize an array. Is that true, and if so, why?
Stepping back a bit, it seems to me that dynamic arrays are better if you need a lot of values, because then you don't need a contiguous block of space in memory. So if you are making small arrays, it seems like a waste of space and time typing to make dynamic arrays. Is this untrue? Should I be doing all arrays from now on as dynamic? This experience certainly changed my opinion on the utility of regular arrays, at least as they pertain to classes.
Also, though I got full credit on the assignment, I feel like I violated the spirit by passing an argument for size (since the literal problem statement reads: "The class constructor should accept an array of test scores as its argument"). Aside from a hardwired global constant or having a size argument, is there a way to pass just the array? I swear I spent a good hour trying to think of a way to do this.
It seems you don't initialize _scores at all. You need _scores = new int[s]; at the top of the constructor (and also delete[] s; in the destructor).
Without initializing _scores, you write things to undefined memory locations.
Without TestScores.h one has to guess, but given what you say about the value of i being corrupted in the loop where you're creating the TestScores objects, that points to your _scores member variable not being properly initialized and when you're trying to load it you are actually trashing memory.
Once TestScores.h is visible, I'll revisit this answer taking the file into account.
Updated now that TestScores.h is available.
The problem is that you are not initializing _scores. You are not actually allocating any memory to hold the array, let alone setting the pointer to point to that memory. So when you attempt to store things into the array you're just trashing memory somewhere.
The first line in your constructor should be:
_scores = new int[_SIZE];
That will allocate memory to hold _SIZE ints and set _scores to point to that memory. Then your assignments to _scores[i] will actually go into defined memory belonging to your program.
Of course, you also have to release this memory (C++ won't do it for you) when instances of TestScore get destroyed. So you will need to define and implement a destructor for TestScores and that destructor needs to contain the line:
delete [] _scores;
This will free the block of memory that _scores points to. You can read docs on the delete operation to see why the [] have to be there in this case.