Since you've used dynamic allocation for this "array" (block of memory), the dimension of the array is not known to the type system. That means you cannot use sizeof(arr). All you've done there is find the size of your int* (a pointer).
And, as you've discovered, trying to null-terminate this array is completely silly because 0 is a perfectly valid, sane integer value.
What you should do is one of the following:
Option 1
const unsigned int LENGTH = 16;
int* ptr = new int[LENGTH];     // (malloc is an ancient approach from C)
for (unsigned int i = 0; i < LENGTH; i++) {
   // ...
}
delete[] ptr;
// here, I've remembered the block's size myself
Option 2
std::vector<int> vec(16);
for (auto myInt : vec) {
   // ...
}
// here, the wrapper called "vector" knows its own size
Option 2a
If you just picked 16 arbitrarily to provide a maximum upper bound, and you don't always need 16 elements:
std::vector<int> vec;   // this grows as needed
vec.push_back(1);
vec.push_back(0);
vec.push_back(3);
for (auto myInt : vec) {
   // ...
}
Option 3
If the value 16 doesn't rely on user input or other runtime data, leave out the dynamic allocation:
std::array<int, 16> arr;
for (auto myInt : arr) {
   // ...
}
Or, to go old-school:
int arr[16];
for (auto myInt : arr) {
   // ...
}
Conclusion
It's hard to tell what you're trying to do, but I think you are trying to have a bunch of array elements, not all of which may be "used" at any one time, and somehow mark the "un-used" ones. In this case, you want to abandon that old-fashioned logic, and use option 2a above.
Bootnote
I've used some C++11 constructs here, namely std::array and the ranged-for loop. Replace the loops with more conventional equivalents as you required.