Why didn't I get a compile time error while accidentally printing only one dimension of a 2D array?
#include <stdio.h>
void main() {
int i;
int arr[2][3] = { 1, 2, 3, 4, 5, 6 }; //<- Declared a 2D array
for (i = 0; i < 6; i++) {
printf("%d\n", arr[i]); // <- Accidently forgot a dimension
}
}
I should have received a compile time error but instead I got a group of addresses! Why? What did arr[0] mean in this context to the compiler?
An expression with array type evaluates to a pointer to the first array element in most contexts (a notable exception, among others, is the sizeof operator).
In your example, arr[i] has array type. So it evaluates to a pointer of type int (*)[] (a pointer to an array). That's what's getting printed. Printing a pointer with %d is undefined behavior, because printf() will read the pointer as if it was an int.
Felix Palmen's answer explains the observed behavior.
Regarding your second question: the reason why you don't get a warning is you did not ask for them.
Compilers are notoriously lenient by default and will accept broken code including obvious undefined behavior. This particular one is not obvious because printf() accepts any number of extra arguments after the initial format string.
You can instruct your compiler to emit many useful warnings to avoid silly mistakes and detect non obvious programming errors.
gcc -Wall -Wextra -Werror
clang -Weverything -Werror
option /W3 or /W4 with Microsoft Visual Studio.
gcc and clang will complain about the sloppy initializer for array arr. It should read:
int arr[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
The print loop is indeed surprising, did you really mean to print the array with a single loop?
Note also that the standard prototype for main without arguments is int main(void).
Related
Okay, so this is a stripped down variant of a bug I had. The bug was that I initialized an array using a variable that wasn't initialized. Earlier I used a function to declare the number of elements using a function, but after a cleanup I forgot about it and moved all declarations to the top of the function.
I used the flags -std=c99 -Wall -Wextra -pedantic -O, and usually gcc warns about values being used before they are uninitialized, but in this specific case it didn't. So, my question is:
Is this a bug in gcc or is it possible for f(&n) to post-initialize the array size in some weird way?
#include <stdio.h>
void f(int * x) {
*x = 8;
}
int main(void) {
int n;
float a[n]; // Compiler should warn that n may contain garbage
a[7] = 3.1415;
printf("%f\n", a[7]);
f(&n); // Removing this causes the compiler warn as expected
return 0;
}
EDIT: It may be this gcc bug?
GCC is accepting float a[n] as a variable-length array. It should, however, warn you that n contains garbage when it’s used. Perhaps VLA initialization is getting rearranged in a way that makes that fact non-obvious to the code generator? If n were initialized before use, moving the call to f() above the declaration of a would clearly be wrong, but this program produces undefined behavior.
Okay, so this is a stripped down variant of a bug I had. The bug was that I initialized an array using a variable that wasn't initialized. Earlier I used a function to declare the number of elements using a function, but after a cleanup I forgot about it and moved all declarations to the top of the function.
I used the flags -std=c99 -Wall -Wextra -pedantic -O, and usually gcc warns about values being used before they are uninitialized, but in this specific case it didn't. So, my question is:
Is this a bug in gcc or is it possible for f(&n) to post-initialize the array size in some weird way?
#include <stdio.h>
void f(int * x) {
*x = 8;
}
int main(void) {
int n;
float a[n]; // Compiler should warn that n may contain garbage
a[7] = 3.1415;
printf("%f\n", a[7]);
f(&n); // Removing this causes the compiler warn as expected
return 0;
}
EDIT: It may be this gcc bug?
GCC is accepting float a[n] as a variable-length array. It should, however, warn you that n contains garbage when it’s used. Perhaps VLA initialization is getting rearranged in a way that makes that fact non-obvious to the code generator? If n were initialized before use, moving the call to f() above the declaration of a would clearly be wrong, but this program produces undefined behavior.
I'm curious about the following expression:
int ints[] = { 1, 2, 3 };
This seems to compile fine even in c89 land with clang. Is there documentation about this? I can't seem to figure out the correct terminology to use when searching for it (and I'd rather not go through and read the entire c89 spec again).
Is this an extension? Is the compiler simply inferring the size of the array?
EDIT: I just remembered you guys like chunks of code that actually compile so here it is:
/* clang tst.c -o tst -Wall -Wextra -Werror -std=c89 */
int main(int argc, const char *argv[]) {
int ints[] = { 1, 2, 3 };
(void)(ints); (void)(argc); (void)(argv);
return 0;
}
It's part of standard C since C89:
§3.5.7 Initialization
If an array of unknown size is initialized, its size is determined by the number of initializers provided for its members. At the end of its initializer list, the array no longer has incomplete type.
In fact, there is an almost exact example:
Example:
The declaration
int x[] = { 1, 3, 5 };
defines and initializes x as a one-dimensional array object that has three members, as no size was specified and there are three initializers.
Is this an extension?
no, this is standard, for all versions of the C standard
by the = the array type is "incomplete" and then is completed by means of the initialization
Is the compiler simply inferring the size of the
array?
yes
This compiles in gcc with no errors or warnings even with -Wall option
meaning that array bounds are checked at run-time and hence compiler can't detect the error
#include<stdio.h>
int main()
{
int a[2][3][4];
a[1][2][100] = 4 ;
return 0;
}
However,
#include<stdio.h>
int main()
{
int a[2][3];
a[1][2][100] = 4 ;
return 0;
}
this generates an error while compiling as :
$ gcc sample.c -Wall
sample.c: In function ‘main’:
sample.c:7: error: subscripted value is neither array nor pointer
Why is this so ? in both the two codes a[1][2][100] is invalid . Still how can the compiler detect this is code2 and not in code1.
Specially when every compiler flattens all multidimensional array into corresponding single dimension arrays, then how can the compiler be selectively aware of this flaw in the code.
Explanation or mention of some book or article where the proper explanation resides will be gratefully accepted :)
The difference is the types. C does no bounds checking but it does do (static) type checking. The type of a in your first example is int[][][] but the type in the second example is int[][].
The "flattening" that you refer to happens in code generation, which is (conceptually, at least) after type checking.
First, array bounds are not checked at runtime or at compile time. Be careful out there.
Second, your second case gives an error because you have a mismatch in array dimension - you're using three subscript operators ([]) on a 2D array. Just because the array happens to be laid out in memory as an array of arrays doesn't mean there is any actual type changing going on with the variable.
Array subscripting is described in the C standard section 6.5.2.1 Array subscripting.
Given
int a[2][3];
the compiler will determine that a[1][2] is of type int. Therefore, accessing element [100] of this is equivalent to:
int x;
x[100] = 4;
This would give you the same error about the subscripted value.
My understand has always been that when I declare an array on the stack with a size that comes in as a variable or parameter, I should get an error.
However, I noticed that I do not get any error if I do not explicitly initialize the array (yes, it won't be on the stack, but I'm wondering about the lack of error).
For example, the following code does not compile because of array2:
#define N 30
void defineArrays(int n)
{
int i,j;
int array1[N] = {};
int array2[n] = {};
for(i=0; i<N; ++i) array1[i] = 0;
for(j=0; j<n; ++j) array2[j] = 0;
}
But the following code compiles and runs, even when I send a real n from main:
#define N 30
void defineArrays(int n)
{
int i,j;
int array1[N] = {};
int array2[n];
for(i=0; i<N; ++i) array1[i] = 0;
for(j=0; j<n; ++j) array2[j] = 0;
}
What I am missing here? Is it declaring array2 as a pointer?
I'm using gcc
Update: Thanks for everyone who answered. The problem was indeed that my version of gcc was defaulting to C99 for some strange reason (or not so strange, maybe I'm just too old), and I incorrectly assumed that it defaults to C90 unless I tell it otherwise.
C99 introduced the ability to have variable length arrays which is now available in GCC (although it's reported as not being totally standards compliant). In the second example, you appear to be taking advantage of that functionality.
Link to GCC's info about variable length arrays: http://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html
I think that you need to choose you C standard version.
gcc -fsyntax-only -std=c89 -pedantic -x c -
<stdin>: In function ‘defineArrays’:
<stdin>:6: warning: ISO C forbids empty initializer braces
<stdin>:8: warning: ISO C90 forbids variable length array ‘array2’
vs.
gcc -fsyntax-only -std=c99 -pedantic -x c -
<stdin>: In function ‘defineArrays’:
<stdin>:6: warning: ISO C forbids empty initializer braces
Declaring the array with an initializer forces the array to be static (created at compile time) even though the scope is within the function. The compiler cannot define the array at compile time because it does not then know the value of 'n'.
When I compile the first example with gcc, it gives me this error:
error: variable-sized object may not be initialized
I imagine this is not allowed because you don't know how big n will be, and therefore you can't be sure that it will be big enough to hold all the elements you are trying to initialize. That is, suppose you have code like this:
int array2[n] = { 1, 2, 3, 4 };
This requires that array2 have (at least) 4 slots. What if n is passed in as zero?
The second example does not have this problem because you are not making any implicit statement about the size of array2.
Hope that helps,
Eric Melski