I understand that when we use sizeof operator on an array name, it gives the total size of the array in bytes. For example
int main(int argc, const char * argv[]) {
int a[][5] = {
{1,2,3,4,5},
{10,20,30,40,50},
{100,200,300,400,500}
};
int n=sizeof(a);
printf("%d\n",n);
}
It gives 60 as output for 15 elements of the array. But when I write
int n=sizeof(*a);
It gives 20 as the output that is the size of the first row while *a is the base address of the 0th element of the 0th row, and its type is a pointer to an integer. And a points to the first row itself. Why is this happening?
*a is row 0 of a, and that row is an array of five int.
In most expressions, an array is automatically converted to a pointer to its first element. Thus, when you use *a in a statement such as int *x = *a;, *a is converted to a pointer to its first element. That results in a pointer to int, which may be assigned to x.
However, when an array is the operand of a sizeof operator, a unary & operator, or an _Alignof_ operator, it is not converted to a pointer to its first element. Also, an array that is a string literal being used to initialize an array is not converted to a pointer (so, in char foo[] = "abc";, "abc" is used as an array to initialize foo; it is not converted to a pointer).
*a is not a pointer, it's an int[5], which is coherent with your reading of 20 assuming a 4-byte int.
Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
The expression a has type "3-element array of 5-element array of int"; thus, sizeof a should yield 3 * 5 * sizeof (int).
The expression *a is the same as the expression a[0] (a[i] is defined as *(a + i) - *a is the same as *(a + 0), which is the same as a[0]). Both *a and a[0] have type "5-element array of int"; thus sizeof *a and sizeof a[0] should both yield 5 * sizeof (int).
However...
If you pass a to a function, such as
foo( a );
then a is not the operand of the sizeof or unary & operators, and the expression will be converted from type "3-element array of 5-element array of int" to "pointer to 5-element array of int":
void foo( int (*a)[5] ) { ... }
If you computed sizeof a in function foo, you would not get 5 * sizeof (int), you would get sizeof (int (*)[5]), which, depending on the platform, would be 4 to 8 bytes.
Similarly, if you passed *a or a[i] to a function, what the function actually receives is a pointer to int, not an array of int, and sizeof will reflect that.
In this 2d array *a is a pointer because when you print it, its seems an address (but it is the 1st column address) :
printf("%d\n", *a);
Output : 9435248
So :
for(int i = 0;i < 3;i++)
printf("%d\n", *a[i]);
The output is :
1
10
100
When you use of *a like this : *a[3] its means you are in 3rd row and 1st column by default.
*a is the address of 1st column and we have 5 column, so when you try this :
sizeof(*a);
Output will be 20 => (5 column) * (int pointer which is 4 byte)).
A 2D array is viewed as an array of 1D arrays. That is, each row in a 2D array is a 1D array. For a given 2D array A, int A[m][n]
you can think of
A[0] as the address of row 0
A[1] as the address of row 1 etc & so on.
Dereferencing can be thought of as below,
A[i][j] = *(A[i] + j) = *(*(A+i) + j)
So when you say *A, it means A[0] which gives you the address of 1st row & not the 1st element of the matrix.
Dereference of A or *A gives the address of row 0 or A[0].
Dereference of A[0] gives the first entry of A or A[0][0] that is
**A = A[0][0].
& since you have 5 elements in the 1st row the size if 20 bytes.
Sizeof returns the size of the variable in memory expressed in bytes. This includes padding (unused bytes added by a compiler to a structure to improve performance). Your array has 15 elements of size 4. The sizeof an integer in memory is 4 in you case. You can easily verify this by running:
printf("sizeof an integer: %zu\n", sizeof(int));
It is always a good idea to use standard int types.
#inlcude <stdint.h>
uint32_t a[][5] = {
{1,2,3,4,5},
{10,20,30,40,50},
{100,200,300,400,500}
};
This will produce exactly the same code but wil clearly show the memory size of the (32 bit) integer.
In your case uint8_t (unsigned int of 8 bit) might be more appropriate. You can read more here. To get the number of elements you should devide the total memory of the array by the sizeof an element.
sizeof(a)/sizeof(a[0])
You can also use a macro to do this:
#define ARRAY_LENGTH(array)(sizeof(array)/sizeof(array[0]))
/*ARRAY_LENGTH(a) will return 15 as expected.*/
You can also look for an answer here: How can I find the number of elements in an array?
Related
This question already has answers here:
How come an array's address is equal to its value in C?
(6 answers)
Closed 4 years ago.
Why is these two are same things I saw one answer to this similar question but couldn't really understand.
Why *(a+i) and a+i doing the same work.
int a[1][2] = {1,2};
printf("%p or %p\n",*(a+0),a+0);
Array subscript brackets "[]" are defined in terms of pointer operations. a[i] is defined as *(a+i), assuming that a is of type array and i is an integer type. The name of an array by itself is evaluated as a pointer to its first element; integer addition to a pointer adds the integer times the sizeof the element to the pointer. (a+i) and *(a+1) are not the same - (a+i) is the address of an element in an array and *(a+i) is that element.
Thus a[0] becomes *(a+0), or *a, the first element of the array. A[1] becomes *(a+1), or *(a + (sizeof (*a) * 1), or *(address of the second element of the array), or just the second element of the array, as one would expect.
Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" is converted ("decays") to an expression of type "pointer to T", and the value of the expression is the address of the first element of the array.
So given a declaration like
int a[100];
any time a appears in an expression where it isn't the operand of the sizeof or unary & operators, the compiler will treat it as a pointer expression equivalent to &a[0] (type int *).
Now, what happens if a is the operand of unary &?
The address of an array is the same as the address of the first element of the array, which should be clear from the diagram below:
+------+
a: | a[0] |
+------+
| a[1] |
+------+
...
+------+
| a[99]|
+------+
The type of the expression &a is int (*)[100] (pointer to 100-element array of int), but the value of the expression is the same as a and &a[0] (address of the first element).
Summarizing that in a table:
int a[100];
Expression Type "Decays" to
---------- ---- -----------
a int [100] int *
*a int n/a
&a int (*)[100] n/a
a[i] int n/a
&a[i] int * n/a
Again, the address of the array is the same as the address of the first element of the array, so &a, a, and &a[0] will all yield the same address value (modulo any type conversions).
Adding 1 to a pointer yields the address of the next object of the pointed to type. IOW, if p points to a 4-byte int object, the result of p + 1 will be the address of the next 4-byte int. If p points to a 100-element array of 4-byte ints, then p + 1 yields the address of the next 100-element array of int.
The subscript operation a[i] is defined as *(a + i) - given the address a, find the address of the i'th object following a and dereference the result.
This means that the value of *a is the same as a[0] - *a == *(a + 0) == a[0].
How does this apply to a 2D array, as in your example?
Given the declaration
int a[1][2];
the expression a "decays" from type "1-element array of 2-element array of int" (int [1][2]) to "pointer to 2-element array of int" (int (*)[2]). The value of the expression a is the address of the first element, but the first element has an array type itself. Thus, that value "decays" to a pointer to the first element of the subarray.
Here's a handy table to summarize:
int a[1][2];
Expression Type "Decays" to
---------- ---- -----------
a int [1][2] int (*)[2]
*a int [2] int *
&a int (*)[1][2] n/a
a[0] int [2] int *
*a[0] int n/a
&a[0] int (*)[2] n/a
a[0][0] int n/a
Again, &a, a, a[0], &a[0], and &a[0][0] will all yield the same value, since the address of a[0][0] is the same as the address of a[0] which is the same as the address of a.
C's doesn't really treat multidimensional arrays differently from single-dimensional ones.
Arrays in C are just array of arrays
char a[2][3][4][5];
is an array 2 of array 3 of array 4 of array 5 of char.
Dereferencing/subscripting works the same for any "Array A of T":
decay A to the address of the first element (or do nothing if you're defering/subscripting a pointer)
add to that the index scaled by sizeof(T)
With dereferencing/subscripting in C, when you speak of one, you speak of the other, because A[Index] or Index[A] is defined to be the same as *(A+Index) or *(Index+A).
6.5.2.1p2
A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero).
Since in char a[2][3][4][5]; a is an array 2 of ( array 3 of array 4 of array 5 of char), a[1] would give you ((char*)&a) + 1 * sizeof(char[3][4][5]) and the result would have type char[3][4][5].
Now here's where arrays are special — arrays aren't first-class objects in C. You can't have an r-value of an array type. When you attempt to obtain one, e.g., by passing the array to a function or an operator, the array immediately decays to a pointer to its first element so the char[3][4][5] type of a[1] immediately changes to char(*)[4][5].
6.5.2.1p3
Successive subscript operators designate an element of a multidimensional array object. If E is an n-dimensional array (n >= 2) with dimensions i x j x . . . x k, then E (used as other than an lvalue) is converted to a pointer to an (n - 1)-dimensional array with dimensions j x . . . x k. If the unary * operator is applied to this pointer explicitly, or implicitly as a result of subscripting, the result is the referenced (n - 1)-dimensional array, which itself is converted into a pointer if used as other than an lvalue. It follows from this that arrays are stored in row-major order (last subscript varies fastest).
This continues recursively until you've chomped off all of the dimensions (from right to left) and are left with a real type that doesn't decay. Effectively the decay of the intermediary arrays means that intermediary derefs/subscripts don't really fetch anything — they're simply additions to a base address.
Some examples with char a[2][3][4][5];:
#include <stdio.h>
char a[2][3][4][5];
#define ASSERT_TP(Expr,Tp) _Generic(Expr,Tp: (char*)(Expr))
int main()
{
printf("%zd\n", ASSERT_TP(a,char(*)[3][4][5])
- (char*)a); //0
printf("%zd\n", ASSERT_TP(a[1],char(*)[4][5])
- (char*)a); //60 == 1 * (3*4*5)
printf("%zd\n", ASSERT_TP(a[1][1],char(*)[5])
- (char*)a); //80 == 1 * (3*4*5) + 1 * (4*5)
}
Applied to your example:
int a[1][2] = {1,2}; // a decays to ptr to 1st element,
//i.e. to `int (*a)[2]`
printf("%p or %p\n",
*(a+0), // == a[0]; ((char*)&a) + 0*sizeof(int[2]);
// type is int[2], which decays to int*
a+0); // == a (after decay); (char*)&a + 0*sizeof(int[2]);
//type is still `int(*)[2]` (because no derefing)
Because the deref in *(a+0) didn't hit the real type yet, there was no fetch, just an addition to a base pointer
with type adjustement. Since the addition added 0, the value didn't change it remained the same as that of a decayed to a pointer to its first element (== a+0) or even &a (which would have the same numerical address but its type would be int (*)[1][2]).
An array can decay to pointer to its first element. In your example plain a will decay to &a[0]. And for any array or pointer a and index i, the expression a[i] is exactly equal to *(a + i).
Also, if you lay out your array how it would look like in memory, it would be
+---------+---------+
| a[0][0] | a[0][1] |
+---------+---------+
Now armed with this information, we can start transforming the expressions in your printf call.
Lets start with *(a + 0):
Because the equivalence, *(a + 0) becomes a[0].
Because a[0] is an array, it will decay to a pointer to its first element, i.e. &a[0][0].
So the first argument is equal to &a[0][0], i.e. a pointer to a[0][0].
Then lets take a + 0.
The expression a + 0 is equal to &*(a + 0).
Because the array/pointer equivalence &*(a + 0) becomes &a[0].
&a[0] is a pointer to the first element of a, which happens to begin at the same position in memory as a[0][0].
This of course means that the pointer &a[0] and &a[0][0] both are pointing to the same locations, and the output would be equal. However the types of those two pointers are very different:
The type of &a[0][0] is a pointer to an int, i.e. int *
The type of &a[0] is a pointer to an array of two int elements, i.e. int (*)[2]
I know, that static arrays are laid out contiguously in memory.
So, for example, int T[10][10] is basically stored the same way as int T[100].
I can access element of index i, j many ways, for example:
int T[10][10];
/*filling array*/
int i=3, j=7;
int x = T[i][j];
//EDIT - old and wrong: int * ptr = T;
int * ptr = &T[0][0];
int y = *(ptr + 10* i + j);
On the other hand, when I create dynamicaly allocated 2-dimensional array by myself:
int ** T;
T = malloc(10 * sizeof(int *));
for(i = 0; i < N; i++)
T[i] = malloc(10 * sizeof(int));
My array contains pointers to
It is obvious, that I can access element of this array by:
int i=3, j=7;
int x = *(*(T+i)+j);
And now my question: why and how does it work for static arrays?
Why does
int T[10][10];
/*filling array*/
int i=3, j=7;
int x = *(*(T+i)+j);
return good value to x, when this table doesn't contain pointers to arrays? *(*(T+i)) shouldn't have sense there in my opinion, end even if, it should return T[0][i], as T points at the first element of array. How does compiler interpret this, is * something other than dereference here? Enlighten me.
For starters:
int * ptr = T;
That's not going to actually work, at least without your compiler yelling at you. Very loudly. The correct way to do this is:
int * ptr = &t[0][0];
This point is actually very relevant to your question.
As you know, when used in an expression, an array gets decayed to a pointer. For example:
char foo[10];
bar(foo);
When used in an expression, like a parameter to a function, the array decays to a pointer to the underlying type. foo gets you a char *, here.
However, and this is the key point: the array only decays one level. If the array is a two-dimensional array, the array does not get decayed to the underlying value, an int in this case. The two dimensional array reference decays to a pointer to a one-dimensional array:
int T[10][10];
/*filling array*/
int i=3, j=7;
int x = *(*(T+i)+j);
The sequence of steps that occurs here:
T decays to a pointer to an array of 10 integers, or int (*)[10]
The addition of i advances the pointer by the given value. The pointer is advanced by the size of the element being pointed to. Since the pointer points to array of 10 integers, the pointer is advanced accordingly. If i was 2, the pointer is advanced by "two arrays of 10 integers", loosely speaking.
The * operator takes a "pointer to an array of 10 integers" and gives you "an array of 10 integers" as a result. In other words: from int (*)[10] to int [10].
Since the result is used in an expression, namely the left operand of + j, and the left operand is an array type, the array type decays to a "pointer to int".
j is added to the result, and dereferenced.
Why does
int T[10][10];
/*filling array*/
int i=3, j=7;
int x = *(*(T+i)+j);
return good value to x
The magic is all in *(*(T+3)+7) (I have converted to the literal values).
T is an array (of size 10) of arrays (of size 10) of int.
When T is used in an an expression it decays into a pointer to its first element, so it decays to "pointer to arrays (of size 10) of int".
Adding an integer to that pointer will advance to the fourth element of the array.
So T+3 is a pointer to an array of 10 ints, and specifically the fourth such array in T.
*(T+3) indirects through that pointer to give an l-value of type "array of 10 ints".
Ah-ha! That is another array being used in an expression - so it decays into a pointer to it's first element! (It wouldn't decay in sizeof, so sizeof(*(T+3)) would be typically 40.)
(*(T+3) + 7) just points at the eight element in the array, and ...
*(*(T+3) + 7) is an l-value of type int!
The following snippet declares a 2-D array of 4 X 10 using malloc function
/* Declare a pointer to an array that has 10
ints in each row. */
int (*p)[10];
register int i, j;
/* allocate memory to hold a 4 x 10 array */
p = malloc(40*sizeof(int));
But I do not understand how does p become a 2-D array. Initially p is declared to be an array of pointers that point to int. What happens after the call to malloc ? I am unable understand this.
In C, pointers and arrays are not the same, despite looking very similar. Here p is of type "pointer to array of 10 ints". You're using it as a "pointer to array of 4 arrays of 10 ints", which is a single block of memory (the only pointer is the outermost pointer). It's basically a dynamically allocated int[4][10].
The trick to reading these definitions is to realize that they're written the same way you use the item. If you have:
*x[10];
The array subscript is applied first, then the pointer dereference. So it's an array of pointers if you define int *x[10]. If you use parenthesis to override normal precedence, you can get the pointer dereference to happen first, so you have a pointer to an array.
Confusing? It gets worse. In function arguments, the outermost array of a function parameter is converted into a pointer.
int *p[10]; // array of 10 pointer to int
int (*p)[10]; // pointer to array of 10 ints
void foo(int *p[10] /* pointer to pointer to int */);
void foo(int (*p)[10] /* pointer to array of 10 ints */);
Further, arrays are converted to pointers when you use them.
int x[10]; // array of 10 int
sizeof(x); // 10 * sizeof(int)
int *y = x; // implicitly converts to a pointer to &x[0]!
sizeof(y); // sizeof(int *)
This means that you can allocate memory for an array of arrays, then let that implicitly convert to a pointer to an array, which you in turn use as if it were an array of arrays!
Anyway, this is all very confusing so please do not ever use this in production code - at least, not without a clarifying typedef:
typedef int vector[3];
vector *array_of_vectors; // actually a pointer to a vector,
// but you can use it as an aray to a vector if enough
// memory is allocated
The memory, worth 40 ints, is reserved to the pointer p. p points at his memory block. It so happens that p chooses to organize this memory as 10 equal parts, each of which happen to hold 4 ints' worth.
That's if this code is actually correct. My C is very rusty at this point.
First, some background information:
Except when it is the operand of the sizeof, _Alignof, or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" is converted ("decays") to an expression of type "pointer to T", and its value is the address of the first element in the array. For example, given the array
int a[10];
anytime the expression a appears in the code, its type will be converted from "10-element array of int" to "pointer to int", or int *, except for cases like sizeof a, _Alignof a, and &a. If we have a 2D array of T, such as
int a[10][10];
the expression a will be converted from type "10-element array of 10-element array of int" to "pointer to 10-element array of int", or int (*)[10] (look familiar? that's the type of your pointer p).
If we want to dynamically allocate an N-element array of type T, we write something like
T *p = malloc(N * sizeof *p);
sizeof *p is equivalent to sizeof (T). In this particular case, type T is "10-element array of int", or int [10]. We want to allocate 4 such arrays, so we can write
int (*p)[10];
p = malloc(4 * sizeof *p);
This allocates space for 4 10-element arrays of int, and assigns the result to p. (sizeof *p == sizeof (int [10])).
So how does this become a 2D array?
Remember that the expression a[i] is equivalent to *(a + i); we find the address of the i'th element of type T following a and dereference the result. In this case p[i] gives us the address of the ith 10-element array of int following p. Since we dereference the pointer as part of the subscript operation, the type of the expression p[i] is "10-element array of int". Thus we can subscript this expression again and get p[i][j].
Can someone explain to me how C retrieves the correct memory address for a row when you only use one subscript to access a 2d array?
Example -
int array2D[2][2] = {1,2,3,4};
printf ( "starting address of row2 = %p" , array2D[1]);
I understand that when subscripting in C that what is actually going on is pointer addition so for a 1d array the array name points to element 0. In this case if I had wanted element 1 the compiler would take the starting address (say 4000) and add 4 to it (assuming a 4 bit int) so that what is returned is the item at memory address 4004.
My understanding is that when you populate a 2d array, as in my example, they are allocated sequentially so I would have
1 2
3 4
at addresses
4000 4004
4008 4012
So how does C work out that in this case array2D[1] should point to 4008 and not 4004? Does it run a sizeof() operator or have I misunderstood a fundamental here?
Thanks in advance
C knows how long each row is, so it does the multiplication to find the row.
int x[][3] = {{1,2,3},{4,5,6}};
then &x[1][0] is &x[0][0] plus 3 * sizeof(int).
That's why in a multidimensional C array declaration, all but the first dimension must be specified.
Pointer arithmetic depends on the type of the element being pointed to. Given a pointer p to type T, p + 1 points to the next element of type T, not necessarily the next byte following p. If T is char, then p + 1 points to the next char object after p, which starts at the byte immediately following p; if T is char [10], then p + 1 points to the next 10-element array of char after p, which starts at the 10th byte following p.
The type of the expression array2d in is "2-element array of 2-element array of int", which "decays" to type "pointer to 2-element array of int", or int (*)[2]1. Thus the expression array2d[1] is interpreted as *(array2d + 1). Since array2d points to an object of type int [2], array2d + 1 points to the next 2-element array of int following array2d, which is 2 * sizeof int bytes away from array2d.
1. Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted to an expression of type "pointer to T" and its value will be the address of the first element in the array.
This is going to be a bit long-winded, but bear with me still.
Array subscription is just a shorthand: (p[N]) equals (*(p + N)) in all contexts for pointer types (both are invalid expressions for void*, though).
Now, if p is an array type, it would decay to a pointer type in an expression like (*(p + N)); an int[2][2] would decay into a pointer of type (*)[2] (i.e. a pointer to an int[2]).
Pointer arithmetic takes types into account; we need to convert things to char* to visualize what the compiler does to us:
T *p;
p[N] equals *(p + N) equals *(T*)((unsigned char*)p + N * sizeof *p)
Now, if T were an int[2] (to equal the situation we described above), then sizeof *p would be sizeof(int[2]), i.e. 2 * sizeof(int).
This is how subscription works in so-called multidimensional arrays.
sizeof(array2D[1]) == 8;
if array2D address is 4000;
so array2D[1] address is 4000+sizeof(array2D[1]) == 4000+8;
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
C sizeof a passed array
instead of sending the sizeof:
void main()
int x[] = {1, 2, 3, 1, 3, 0, 6};
dupes(x, sizeof x / sizeof x[0]);
...
}
int dupes(int a[], int n)
...
...
why cant i call it with only the array:
int main()
{
int x[] = {1, 2, 3, 1, 3, 0, 6};
dupes(x);
...
}
int dupes(int a[])
{
int n = sizeof a / sizeof a[0];
when i do this the size always gets 1 - and it cant be 1 casue i'm sending an array with 7 elements!
sizeof is not a function, it a compiler operator (so you don't "call" sizeof, you just use the sizeof operator). The compiler has no way to know the size of the formal argument a for your function dupes (and treat is as int* a, i.e. a pointer formal).
sizeof is always computed at compile time.
Arrays decay into a pointer to their first element when they are passed to a function. sizeof only works if you pass the actual original array.
In other languages array references always carry the length along with them but the C philosophy is against this kind of hidden preformance penalty so you have to pass the length yourself.
This prototype in C:
int dupes(int a[])
{
}
is equivalent to this prototype:
int dupes(int *a)
{
}
You array argument a is converted to a pointer to int.
Regarding function declarators, this appears in 6.7.5.3p7 in C99 standard
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to
‘‘qualified pointer to type’’, where the type qualifiers (if any) are
those specified within the [ and ] of the array type derivation.
Arrays in C are second class citizens, you cannot pass arrays to functions. You can just pass a pointer to the first element of the array.
So when you compute sizeof a, the result is the size of the pointer and not the size of the array.
sizeof a / sizeof a[0] in the body of your function actually computes this: sizeof (int *) / sizeof (int) which yields 1 in your system, because the size of an int * is the same as the size of an int in your system.
It's because when You pass an array to a function, it is copied into a pointer and the information about it's size is lost. In the called function it is just a normal pointer.
So size of (a) becomes 4 (because its a pointer) and sizeof (a[0]) is also 4 (because its an integer), so n equals 1
When an expression of type "N-element array of T" appears in most contexts, it is implicitly converted to an expression of type "pointer to T" and its value is the address of the first element in the array. The only exceptions to this rule occur when the array expression is an operand of the sizeof and unary & operators, or is a string literal being used to initialize an array in a declaration.
When you call dupes, the expression x is converted from type "7-element array of int" to "pointer to int", so the dupes function receives a pointer value, not an array. In the context of a function parameter declaration, T a[N] and T a[] are equivalent to T *a.
So in the dupes function, sizeof a / sizeof a[0] is equivalent to sizeof (int *) / sizeof (int), which in your case is 1.
sizeof a[0]; retruns the first element of the array
and when you pass an array it returns the address of the first element of the array so you have to specify the length also.
internally traversing an array as follow:
int a[]={1,2,3};
when you pass a[] as a function argument it sends the address of the element a[0] that is internal address of the 1 and calculates a[1] as
address of a[0] + size of int (type of an array)
when you type sizeof(a[]) it will return the sizeof(int pointer) as a[0] contains the internal address(integer pointer).
It's because you cannot pass an array directly to a function. You can only pass a pointer to an element within such an array.
In function parameters, the [] does not mean array, it means a pointer
int dupes(int a[])
is exactly the same as
int dupes(int *a)
When you use the name of an array as a value, it actually means the pointer to the 1. element in that array. And a pointer is just that, a pointer. It doesn't know it's pointing into an array of a given size.
So e.g. int *a = x; will assign a pointer to the first element in x to a- Same thing when you call a function, dupes(x) will pass a pointer to the first element in x as the argument to the dupes function. This is exactly the same as calling dupes(&x[0])
Inside the dupes() function, its argument is just a pointer. So sizeof x/sizeof x[0] is equivialent to sizeof(int*)/sizeof(int), which is 1 if your pointers are the same size as an int.
(However, since you passed a pointer to the 1. element in an array, you can increment that pointer to access the other elements. e.g. x + 2 will make x point to the 3. element (counting from 1) in that array. And if you dereference that pointer, you get its value: *(x + 2). And remember that as a value, x[2] is the same as *(x + 2)