Every C programmer can determine the number of elements in an array with this well-known macro:
#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a])
Here is a typical use case:
int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19};
printf("%lu\n", NUM_ELEMS(numbers)); // 8, as expected
However, nothing prevents the programmer from accidentally passing a pointer instead of an array:
int * pointer = numbers;
printf("%lu\n", NUM_ELEMS(pointer));
On my system, this prints 2, because apparently, a pointer is twice as large as an integer. I thought about how to prevent the programmer from passing a pointer by mistake, and I found a solution:
#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a]))
This works because a pointer to an array has the same value as a pointer to its first element. If you pass a pointer instead, the pointer will be compared with a pointer to itself, which is almost always false. (The only exception is a recursive void pointer, that is, a void pointer that points to itself. I can live with that.)
Accidentally passing a pointer instead of an array now triggers an error at runtime:
Assertion `(void*)&(pointer) == (void*)(pointer)' failed.
Nice! Now I have a couple of questions:
Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression? Sorry if this is a dumb question :)
Can the check somehow be done at compile-time?
My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?
Am I the first to think of this? If so, how many virgins can I expect to be waiting for me in heaven? :)
Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?
Yes, it is valid as the left operand of the comma operator can be an expression of type void. And assert function has void as its return type.
My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?
It believes so because the result of a comma expression is never a constant expression (e..g, 1, 2 is not a constant expression).
EDIT1: add the update below.
I have another version of your macro which works at compile time:
#define NUM_ELEMS(arr) \
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \
+ sizeof (arr) / sizeof (*(arr)))
and which seems to work even also with initializer for object with static storage duration.
And it also work correctly with your example of int b[NUM_ELEMS(a)]
EDIT2:
to address #DanielFischer comment. The macro above works with gcc without -pedantic only because gcc accepts :
(void *) &arr == arr
as an integer constant expression, while it considers
(void *) &ptr == ptr
is not an integer constant expression. According to C they are both not integer constant expressions and with -pedantic, gcc correctly issues a diagnostic in both cases.
To my knowledge there is no 100% portable way to write this NUM_ELEM macro. C has more flexible rules with initializer constant expressions (see 6.6p7 in C99) which could be exploited to write this macro (for example with sizeof and compound literals) but at block-scope C does not require initializers to be constant expressions so it will not be possible to have a single macro which works in all cases.
EDIT3:
I think it is worth mentioning that the Linux kernel has an ARRAY_SIZE macro (in include/linux/kernel.h) that implements such a check when sparse (the kernel static analysis checker) is executed.
Their solution is not portable and make use of two GNU extensions:
typeof operator
__builtin_types_compatible_p builtin function
Basically it looks like something like that:
#define NUM_ELEMS(arr) \
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \
+ sizeof (arr) / sizeof (*(arr)))
Yes. The left expression of a comma operator is always evaluated as a void expression (C99 6.5.17#2). Since assert() is a void expression, no problem to begin with.
Maybe. While the C preprocessor doesn't know about types and casts and can't compare addresses you can use the same trick as for evaluating sizeof() at compile time, e.g. declaring an array the dimension of which is a boolean expression. When 0 it is a constraint violation and a diagnostic must be issued. I've tried it here, but so far have not been successful... maybe the answer actually is "no".
No. Casts (of pointer types) are not integer constant expressions.
Probably not (nothing new under the Sun these days). An indeterminate number of virgins of indeterminate sex :-)
Related
How does the __is_constexpr(x) macro of the Linux Kernel work? What is its purpose? When was it introduced? Why was it introduced?
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker#med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
For a discussion on different approaches to solve the same problem, see instead: Detecting Integer Constant Expressions in Macros
Linux Kernel's __is_constexpr macro
Introduction
The __is_constexpr(x) macro can be found in Linux Kernel's include/kernel/kernel.h:
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker#med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
It was introduced during the merge window for Linux Kernel v4.17, commit 3c8ba0d61d04 on 2018-04-05; although the discussions around it started a month before.
The macro is notable for taking advantage of subtle details of the C standard: the conditional operator's rules for determining its returned type (6.5.15.6) and the definition of a null pointer constant (6.3.2.3.3).
In addition, it relies on sizeof(void) being allowed (and different than sizeof(int)), which is a GNU C extension.
How does it work?
The macro's body is:
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
Let's focus on this part:
((void *)((long)(x) * 0l))
Note: the (long)(x) cast is intended to allow x to have pointer types and to avoid warnings on u64 types on 32-bit platforms. However, this detail is not important for understanding the key points of the macro.
If x is an integer constant expression (6.6.6), then it follows that ((long)(x) * 0l) is an integer constant expression of value 0. Therefore, (void *)((long)(x) * 0l) is a null pointer constant (6.3.2.3.3):
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant
If x is not an integer constant expression, then (void *)((long)(x) * 0l) is not a null pointer constant, regardless of its value.
Knowing that, we can see what happens afterwards:
8 ? ((void *)((long)(x) * 0l)) : (int *)8
Note: the second 8 literal is intended to avoid compiler warnings about creating pointers to unaligned addresses. The first 8 literal could simply be 1. However, these details are not important for understanding the key points of the macro.
The key here is that the conditional operator returns a different type depending on whether one of the operands is a null pointer constant (6.5.15.6):
[...] if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.
So, if x was an integer constant expression, then the second operand is a null pointer constant and therefore the type of the expression is the type of the third operand, which is a pointer to int.
Otherwise, the second operand is a pointer to void and thus the type of the expression is a pointer to void.
Therefore, we end up with two possibilities:
sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression
sizeof(int) == sizeof(*((void *)(....))) // otherwise
According to the GNU C extension, sizeof(void) == 1. Therefore, if x was an integer constant expression, the result of the macro is 1; otherwise, 0.
Moreover, since we are only comparing for equality two sizeof expressions, the result is itself another integer constant expression (6.6.3, 6.6.6):
Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.
An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.
Therefore, in summary, the __is_constexpr(x) macro returns an integer constant expression of value 1 if the argument is an integer constant expression. Otherwise, it returns an integer constant expression of value 0.
Why was it introduced?
The macro came to be during the effort to remove all Variable Length Arrays (VLAs) from the Linux kernel.
In order to facilitate so, it was desirable to enable GCC's -Wvla warning kernel-wide; so that all instances of VLAs were flagged by the compiler.
When the warning was enabled, it turned out that GCC reported many cases of arrays being VLAs which was not intended to be so. For instance in fs/btrfs/tree-checker.c:
#define BTRFS_NAME_LEN 255
#define XATTR_NAME_MAX 255
char namebuf[max(BTRFS_NAME_LEN, XATTR_NAME_MAX)];
A developer may expect that max(BTRFS_NAME_LEN, XATTR_NAME_MAX) was resolved to 255 and therefore it should be treated as a standard array (i.e. non-VLA). However, this depends on what the max(x, y) macro expands to.
The key issue is that GCC generates VLA-code if the array's size is not an (integer) constant expression as defined by the C standard. For instance:
#define not_really_constexpr ((void)0, 100)
int a[not_really_constexpr];
According to the C90 standard, ((void)0, 100) is not a constant expression (6.6), due to the comma operator being used (6.6.3). In this case, GCC opts to issue VLA code, even when it knows the size is a compile-time constant. Clang, in contrast, does not.
Since the max(x, y) macro in the kernel was not a constant expression, GCC triggered the warnings and generated VLA code where kernel developers did not intend it.
Therefore, a few kernel developers tried to develop alternative versions of the max and other macros to avoid the warnings and the VLA code. Some attempts tried to leverage GCC's __builtin_constant_p builtin, but no approach worked with all the versions of GCC that the kernel supported at the time (gcc >= 4.4).
At some point, Martin Uecker proposed a particularly clever approach that did not use builtins (taking inspiration from glibc's tgmath.h):
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
While the approach uses a GCC extension, it was nevertheless well-received and was used as the key idea behind the __is_constexpr(x) macro which appeared in the kernel after a few iterations with other developers. The macro was used then to implement the max macro and other macros that are required to be constant expressions in order to avoid GCC generating VLA code.
How does the __is_constexpr(x) macro of the Linux Kernel work? What is its purpose? When was it introduced? Why was it introduced?
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker#med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
For a discussion on different approaches to solve the same problem, see instead: Detecting Integer Constant Expressions in Macros
Linux Kernel's __is_constexpr macro
Introduction
The __is_constexpr(x) macro can be found in Linux Kernel's include/kernel/kernel.h:
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker#med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
It was introduced during the merge window for Linux Kernel v4.17, commit 3c8ba0d61d04 on 2018-04-05; although the discussions around it started a month before.
The macro is notable for taking advantage of subtle details of the C standard: the conditional operator's rules for determining its returned type (6.5.15.6) and the definition of a null pointer constant (6.3.2.3.3).
In addition, it relies on sizeof(void) being allowed (and different than sizeof(int)), which is a GNU C extension.
How does it work?
The macro's body is:
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
Let's focus on this part:
((void *)((long)(x) * 0l))
Note: the (long)(x) cast is intended to allow x to have pointer types and to avoid warnings on u64 types on 32-bit platforms. However, this detail is not important for understanding the key points of the macro.
If x is an integer constant expression (6.6.6), then it follows that ((long)(x) * 0l) is an integer constant expression of value 0. Therefore, (void *)((long)(x) * 0l) is a null pointer constant (6.3.2.3.3):
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant
If x is not an integer constant expression, then (void *)((long)(x) * 0l) is not a null pointer constant, regardless of its value.
Knowing that, we can see what happens afterwards:
8 ? ((void *)((long)(x) * 0l)) : (int *)8
Note: the second 8 literal is intended to avoid compiler warnings about creating pointers to unaligned addresses. The first 8 literal could simply be 1. However, these details are not important for understanding the key points of the macro.
The key here is that the conditional operator returns a different type depending on whether one of the operands is a null pointer constant (6.5.15.6):
[...] if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.
So, if x was an integer constant expression, then the second operand is a null pointer constant and therefore the type of the expression is the type of the third operand, which is a pointer to int.
Otherwise, the second operand is a pointer to void and thus the type of the expression is a pointer to void.
Therefore, we end up with two possibilities:
sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression
sizeof(int) == sizeof(*((void *)(....))) // otherwise
According to the GNU C extension, sizeof(void) == 1. Therefore, if x was an integer constant expression, the result of the macro is 1; otherwise, 0.
Moreover, since we are only comparing for equality two sizeof expressions, the result is itself another integer constant expression (6.6.3, 6.6.6):
Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.
An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.
Therefore, in summary, the __is_constexpr(x) macro returns an integer constant expression of value 1 if the argument is an integer constant expression. Otherwise, it returns an integer constant expression of value 0.
Why was it introduced?
The macro came to be during the effort to remove all Variable Length Arrays (VLAs) from the Linux kernel.
In order to facilitate so, it was desirable to enable GCC's -Wvla warning kernel-wide; so that all instances of VLAs were flagged by the compiler.
When the warning was enabled, it turned out that GCC reported many cases of arrays being VLAs which was not intended to be so. For instance in fs/btrfs/tree-checker.c:
#define BTRFS_NAME_LEN 255
#define XATTR_NAME_MAX 255
char namebuf[max(BTRFS_NAME_LEN, XATTR_NAME_MAX)];
A developer may expect that max(BTRFS_NAME_LEN, XATTR_NAME_MAX) was resolved to 255 and therefore it should be treated as a standard array (i.e. non-VLA). However, this depends on what the max(x, y) macro expands to.
The key issue is that GCC generates VLA-code if the array's size is not an (integer) constant expression as defined by the C standard. For instance:
#define not_really_constexpr ((void)0, 100)
int a[not_really_constexpr];
According to the C90 standard, ((void)0, 100) is not a constant expression (6.6), due to the comma operator being used (6.6.3). In this case, GCC opts to issue VLA code, even when it knows the size is a compile-time constant. Clang, in contrast, does not.
Since the max(x, y) macro in the kernel was not a constant expression, GCC triggered the warnings and generated VLA code where kernel developers did not intend it.
Therefore, a few kernel developers tried to develop alternative versions of the max and other macros to avoid the warnings and the VLA code. Some attempts tried to leverage GCC's __builtin_constant_p builtin, but no approach worked with all the versions of GCC that the kernel supported at the time (gcc >= 4.4).
At some point, Martin Uecker proposed a particularly clever approach that did not use builtins (taking inspiration from glibc's tgmath.h):
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
While the approach uses a GCC extension, it was nevertheless well-received and was used as the key idea behind the __is_constexpr(x) macro which appeared in the kernel after a few iterations with other developers. The macro was used then to implement the max macro and other macros that are required to be constant expressions in order to avoid GCC generating VLA code.
This question already has answers here:
Why does sizeof(x++) not increment x?
(10 answers)
Closed 7 years ago.
The C code likes this:
#include <stdio.h>
#include <unistd.h>
#define DIM(a) (sizeof(a)/sizeof(a[0]))
struct obj
{
int a[1];
};
int main()
{
struct obj *p = NULL;
printf("%d\n",DIM(p->a));
return 0;
}
This object pointer p is NULL, so, i think this p->a is illegal.
But i have tested this code in Ubuntu14.04, it can execute correctly. So, I want to know why...
Note: the original code had int a[0] above but I've changed that to int a[1] since everyone seems to be hung up on that rather than the actual question, which is:
Is the expression sizeof(p->a) valid when p is equal to NULL?
Because sizeof is a compile time construction, it does not depend on evaluating the input. sizeof(p->a) gets evaluated based on the declared type of the member p::a solely, and becomes a constant in the executable. So the fact that p points to null makes no difference.
The runtime value of p plays absolutely no role in the expression sizeof(p->a).
In C and C++, sizeof is an operator and not a function. It can be applied to either a type-id or an expression. Except in the case that of an expression and the expression is a variable-length array (new in C99) (as pointed out by paxdiablo), the expression is an unevaluated operand and the result is the same as if you had taken sizeof against the type of that expression instead. (C.f. C11 references due to paxdiablo below, C++14 working draft 5.3.3.1)
First up, if you want truly portable code, you shouldn't be attempting to create an array of size zero1, as you did in your original question, now fixed. But, since it's not really relevant to your question of whether sizeof(p->a) is valid when p == NULL, we can ignore it for now.
From C11 section 6.5.3.4 The sizeof and _Alignof operators (my bold):
2/ The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.
Therefore no evaluation of the operand is done unless it's a variable length array (which your example is not). Only the type itself is used to figure out the size.
1 For the language lawyers out there, C11 states in 6.7.6.2 Array declarators (my bold):
1/ In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero.
However, since that's in the constraints section (where shall and shall not do not involve undefined behaviour), it simply means the program itself is not strictly conforming. It's still covered by the standard itself.
This code contains a constraint violation in ISO C because of:
struct obj
{
int a[0];
};
Zero-sized arrays are not permitted anywhere. Therefore the C standard does not define the behaviour of this program (although there seems to be some debate about that).
The code can only "run correctly" if your compiler implements a non-standard extension to allow zero-sized arrays.
Extensions must be documented (C11 4/8), so hopefully your compiler's documentation defines its behaviour for struct obj (a zero-sized struct?) and the value of sizeof p->a, and whether or not sizeof evaluates its operand when the operand denotes a zero-sized array.
sizeof() doesn't care a thing about the content of anything, it merely looks at the resulting type of the expression.
Since C99 and variable length arrays, it is computed at run time when a variable length array is part of the expression in the sizeof operand.Otherwise, the operand is not evaluated and the result is an integer constant
Zero-size array declarations within structs was never permitted by any C standard, but some older compilers allowed it before it became standard for compilers to allow incomplete array declarations with empty brackets(flexible array members).
This question already has answers here:
Why is sizeof considered an operator?
(10 answers)
Closed 8 years ago.
In c, we are using the sizeof() for getting the size of the datatypes. So
how it is defined. It is a macro or a function.
Because we can use that as two ways,
sizeof int
and
sizeof(int)
so how this is defined in header file.
It's neither. It's a built-in operator, whose value is computed at compile-time unless the argument is the name of a variable-length array (added in C99).
The parentheses that you often see are not part of the "call", since sizeof is not a function. They are part of the argument, and are only needed when the argument is a cast expression, i.e. the name of a type enclosed in parentheses.
I personally recommend against using sizeof with a type name as the argument whenever possible, since it's usually not needed, and creates a disconnect/de-coupling which can lead to errors.
Consider something like this:
float *vector = malloc(100 * sizeof(double));
The above, of course, contains a bug: if float is smaller than double, it will waste a lot of memory. It's easy to imagine ending up with something like the above, if vector started out as an array of double but was later changed to float. To protect aginst this, I always write:
float *vector = malloc(10 * sizeof *vector);
The above uses the argument *vector (an expression of type float) to sizeof, which is not a type name so no parentheses are needed. It also "locks" the size of the element to the pointer used to hold it, which is safer.
Sizeof is neither a macro nor a function.Its a operator which is evaluated at compile time.
Macros evaluated during pr-processing phase.
As pointed out by #Yu Hao Variable length arrays is the only exception.
For More Understanding solve this;
#include<stdio.h>
char func(char x)
{
x++;
return x;
}
int main()
{
printf("%zu", sizeof(func(3)));
return 0;
}
A) 1 B)2 C)3 D)4
From ISO/IEC9899
6.5.3.4 The sizeof operator
Constraints
1 The sizeof operator shall not be applied to an expression that has function type or an
incomplete type, to the parenthesized name of such a type, or to an expression that
designates a bit-field member.
So it is neither a macro nor a function.Its a operator!
and the way it is handled is a thing of the compiler.
But regarding to compile time and runtime determination the standard says:
Semantics
2 The sizeof operator yields the size (in bytes) of its operand, which may be an
expression or the parenthesized name of a type. The size is determined from the type of
the operand. The result is an integer. If the type of the operand is a variable length array
type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an
integer constant.
So it is even given by standard that it mus be determined on compile time excepting the VLA case.
Syntax
sizeof( type )
List sizeof expression
Both versions return a constant of type std::size_t.
Explanation
returns size in bytes of the object representation of type.
returns size in bytes of the object representation of the type, that
would be returned by expression, if evaluated.
The unary operator sizeof is used to calculate the size of any datatype, measured in the number of bytes required to represent the type.
In many programs, there are situations where it is useful to know the size of a particular datatype (one of the most common examples is dynamic memory allocation using the library function malloc). Though for any given implementation of C or C++ the size of a particular datatype is constant, the sizes of even primitive types in C and C++ are implementation-defined (that is, not precisely defined by the standard). This can cause problems when trying to allocate a block of memory of the appropriate size. For example, say a programmer wants to allocate a block of memory big enough to hold ten variables of type int. Because our hypothetical programmer doesn't know the exact size of type int, the programmer doesn't know how many bytes to ask malloc for. Therefore, it is necessary to use sizeof:
Why is sizeof considered an operator and not a function?
What property is necessary to qualify as an operator?
Because the C standard says so, and it gets the only vote.
As consequences:
The operand of sizeof can be a parenthesised type, sizeof (int), instead of an object expression.
The parentheses are unnecessary: int a; printf("%d\n", sizeof a); is perfectly fine. They're often seen, firstly because they're needed as part of a type cast expression, and secondly because sizeof has very high precedence, so sizeof a + b isn't the same as sizeof (a+b). But they aren't part of the invocation of sizeof, they're part of the operand.
You can't take the address of sizeof.
The expression which is the operand of sizeof is not evaluated at runtime (sizeof a++ does not modify a).
The expression which is the operand of sizeof can have any type except void, or function types. Indeed, that's kind of the point of sizeof.
A function would differ on all those points. There are probably other differences between a function and a unary operator, but I think that's enough to show why sizeof could not be a function even if there was a reason to want it to be.
It can be used as a compile-time constant, which is only possible if it's an operator rather than a function. For instance:
union foo {
int i;
char c[sizeof(int)];
};
Syntactically if it weren't an operator then it would have to be a preprocessor macro since functions can't take types as arguments. That would be a difficult macro to implement since sizeof can take both types and variables as an argument.
Because the C standard says so, and it gets the only vote.
And the standard is probably correct because sizeof takes a type and
In general, if either the domain or codomain (or both) of a function contains elements significantly more complex than real numbers, that function is referred to as an operator. Conversely, if neither the domain nor the codomain of a function contain elements more complicated than real numbers, that function is likely to be referred to simply as a function. Trigonometric functions such as cosine are examples of the latter case.
Additionally, when functions are used so often that they have evolved faster or easier notations than the generic F(x,y,z,...) form, the resulting special forms are also called operators. Examples include infix operators such as addition "+" and division "/", and postfix operators such as factorial "!". This usage is unrelated to the complexity of the entities involved.
(Wikipedia)
Because it's not a function. You can use it like that:
int a;
printf("%d\n", sizeof a);
Function does have entry point, code, etc. Function is to be run at runtime (or inlined), sizeof has to be determined at compile-time.
sizeof operator is compile time entity not runtime and don't need parenthesis like a function. When code is compiled then it replace the value with the size of that variable at compile time but in function after function gets execute then we will know the returning value.
Because:
when you pass a value to a function, the size of the object is not passed to the function, so a sizeof "function" would have no way of determining the size
in C, functions can only accept one type of argument; sizeof() needs to accept all sorts of differnet things (variables as well as types! You can't pass a type to a function in C)
calling a function involves making a copy of the arguments and other unnecessary overhead
There is small diference from function - value of sizeof is resolved on compile time, but not at runtime!
Because it is a compile-time operator that, in order to calculate the size of an object, requires type information that is only available at compile-time. This doesn't hold for C++.
sizeof() operator is a compile time would be occurrence. It can be used to determine the parameters or arguments.
Sizeof(), I think obviously it's both a function and an operator. Why? Because a function holds parentheses for entry at the stage of entry. But mainly also an operator cause operators are action character, therefore sizeof is an action statement that acts on the operand in the parentheses.