I was bug-fixing some code and the compiler warned (legitimately) that the function dynscat() was not declared — someone else's idea of an acceptable coding standard — so I tracked down where the function is defined (easy enough) and which header declared it (none; Grrr!). But I was expecting to find the details of the structure definition were necessary for the extern declaration of qqparse_val:
extern struct t_dynstr qqparse_val;
extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);
void qqcat(char *s)
{
dynscat(&qqparse_val, s);
if (*s == ',')
dynscat(&qqparse_val, "$");
}
The qqcat() function in the original code was static; the extern declaration quells the compiler warning for this snippet of the code. The dynscat() function declaration was missing altogether; again, adding it quells a warning.
With the code fragment shown, it's clear that only the address of the variable is used, so it makes sense at one level that it does not matter that the details of the structure are not known. Were the variable extern struct t_dynstr *p_parseval;, you'd not be seeing this question; that would be 100% expected. If the code needed to access the internals of the structure, then the structure definition would be needed. But I'd always expected that if you declared that the variable was a structure (rather than a pointer to the structure), the compiler would want to know the size of the structure — but apparently not.
I've tried provoking GCC into complaining, but it doesn't, even GCC 4.7.1:
gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c
The code has been compiling on AIX, HP-UX, Solaris, Linux for a decade, so it isn't GCC-specific that it is accepted.
Question
Is this allowed by the C standard (primarily C99 or C11, but C89 will do too)? Which section? Or have I just hit on an odd-ball case that works on all the machines it's ported to but isn't formally sanctioned by the standard?
What you have is an incomplete type (ISO/IEC 9899:1999 and 2011 — all these references are the same in both — §6.2.5 ¶22):
A structure or union type of unknown content (as described in §6.7.2.3) is an incomplete
type.
An incomplete type can still be an lvalue:
§6.3.2.1 ¶1 (Lvalues, arrays, and function designators)
An lvalue is an expression with an object type or an incomplete type other than void; ...
So as a result it's just like any other unary & with an lvalue.
Looks like a case of taking the address of an object with incomplete type.
Using pointers to incomplete types is totally sane and you do it each time you use a pointer-to-void (but nobody ever told you :-)
Another case is if you declare something like
extern char a[];
It is not surprising that you can assign to elements of a, right? Still, it is an incomplete type and compilers will tell you as soon as you make such an identifier the operand of a sizeof.
your line
extern struct t_dynstr qqparse_val;
Is an external declaration of an object, and not a definition. As an external object it "has linkage" namely external linkage.
The standard says:
If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator, ...
this implies that if it has linkage the type may be incomplete. So there is no problem in doing &qqparse_val afterwards. What you wouldn't be able to do would be sizeof(qqparse_val) since the object type is incomplete.
A declaration is necessary to "refer" to something.
A definition is necessary to "use" something.
A declaration may provide some limited definition as in "int a[];"
What stumps me is:
int f(struct _s {int a; int b;} *sp)
{
sp->a = 1;
}
gcc warn the 'struct _s' is declared inside parameter list. And states "its scope is ONLY this definition or declaration,...". However it does not give an error on "sp->a" which isn't in the parameter list. When writing a 'C' parser I had to decide where the definition scope ended.
Focusing on the first line:
extern struct t_dynstr qqparse_val;
It can be divided into the separate steps of creating the type and the variable, resulting in this equivalent pair of lines:
struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
extern struct t_dynstr qqparse_val; /* declaration of an object of that type */
The second line looks just like the original, but now it's referring to the type that already exists because of the first line.
The first line works because that's just how opaque structs are done.
The second line works because you don't need a complete type to do an extern declaration.
The combination (second line works without the first line) works because combining a type declaration with a variable declaration works in general. All of these are using the same principle:
struct { int x,y; } loc; /* define a nameless type and a variable of that type */
struct point { int x,y; } location; /* same but the type has a name */
union u { int i; float f; } u1, u2; /* one type named "union u", two variables */
It looks a little funny with the extern being followed immediately by a type declaration, like maybe you're trying to make the type itself "extern" which is nonsense. But that's not what it means. The extern applies to the qqparse_val in spite of their geographical separation.
Here's my thoughts relative to the standard (C11).
Section 6.5.3.2: Address and indirection operators
Constraints
Paragraph 1: The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.
Paragraph 2: The operand of the unary * operator shall have pointer type.
Here, we don't specify any requirement on the object, other than that it is an object (and not a bitfield or register).
On the other hand, let's look at sizeof.
6.5.3.4 The sizeof and _Alignof operators
Constraints
Paragraph 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. The _Alignof operator shall not be applied to a function type or an incomplete type.
Here, the standard explicitly requires the object to not be an incomplete type.
Therefore, I think this is a case of what is not explicitly denied is allowed.
Related
I. Background for the question.
As for 6.7.8., 4) of the Standard:
All the expressions in an initializer for an object that has static
storage duration shall be constant expressions or string literals.
As for 6.2.4. 3) of the Standard:
An object whose identifier is declared with external or
internal linkage, or with the storage-class specifier static
has static storage duration.
As for 6.6. 7) of the Standard:
More latitude is permitted for constant expressions in
initializers.
and 6.6. 9):
An address constant is a null pointer,a pointer to an lvalue designating
an object of static storage duration, or a pointer to a function
designator; ...
Finally, in 6.3.2.1. 1) we read:
An lvalue is an expression with an object type or an incomplete type
other than void;
II. Question.
I have the following declarations in object.h :
struct class_check_s {
char blank;
};
struct class_check_s class_check = { 0 };
struct class_check_s *passed = &class_check;
struct object_vt_s {
/* ... */
struct class_check_s *safecheck;
/* ... */
};
object.h is included into object.c , in which i have the following piece of code:
struct object_vt_s object_vt = {
/* ... */
.safecheck= passed,
/* ... */
};
gcc is yelling at .safecheck= passed part:
error: initializer element is not constant
clang is similar:
error: initializer element is not a compile-time constant
But i thought that my situation is falling under 6.6. 9). I guess not? I cannot find the answer why exactly though.
What i am doing is kinda a little oop module mostly for studying purposes. I am trying to achieve some sort of a dynamic type checking inside of objects' "methods".
What 6.6 9 means by “a pointer to an lvalue…” is a pointer-value for an lvalue, that is, the address of an lvalue. While your struct class_check_s *passed is called a “pointer,” it is more specifically an object whose value is a pointer. When you write .safecheck = passed, you are not giving the compiler the value it wants for the initializer; you are telling it to get the value from passed.
You could write .safecheck = &class_check. This initializes safecheck to the address of class_check, and the compiler is able to describe that address to the linker so that the program loader can correctly adjust its value when the program starts.
However, with .safecheck = passed, the compiler nominally has to read the value of passed from storage. In theory, a good compiler could look at the initialization of passed, see it is initialized to &class_check, and use that value for the initialization. The C standard does not formally prohibit this, but it does not require it, and it would be unusual. Additionally, note the semantics here are a bit wonky: passed is not declared const, so its value could change, so why should any particular value of it be used when .safecheck = passed is compiled? Sure, it is all “compile-time” stuff, but that is adding semantics to the C language that are not specified by the C standard. As far as the semantics of C goes, passed does not exist (no storage is reserved for it) until program execution starts.
The void type in C seems to be strange from various different situations. Sometimes it behaves like a normal object type, such as int or char, and sometimes it just means nothing (as it should).
Look at my snippet. First of all, it seems strange that you can declare a void object, meaning you just declare nothing.
Then I created an int variable and casted its result to void, discarding it:
If an expression of any other type is evaluated as a void
expression, its value or designator is discarded. (ISO/IEC 9899:201x, 6.3.2.2 void)
I tried to call my function with a void cast, but my compiler gave me (Clang 10.0):
error: too many arguments to function call, expected 0, have 1
So the void in a prototype means nothing, and not the type void.
But then, I created a pointer to void, dereferenced it, and assigning the “result” to my int variable. I got the “incompatible type” error. That means the void type does exist here.
extern void a; // Why is this authorised ???
void foo(void); // This function takes no argument. Not the 'void' type.
int main(void)
{
int a = 42;
void *p;
// Expression result casted to 'void' which discards it (per the C standard).
(void)a;
// Casting to 'void' should make the argument inexistant too...
foo((void)a);
// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
// Am I not passing the 'void' type ?
foo(*p);
return 0;
}
Is void an actual type, or a keyword to means nothing ? Because sometimes it behaves like the instruction “nothing is allowed here”, and sometimes like an actual type.
EDIT: This questions is NOT a duplicate. It is a purely about the semantics of the void type. I do not want any explanation about how to use void, pointers to void or any other things. I want an answer per the C standard.
In C language the void type has been introduced with the meaning of 'don't care' more than 'null' or 'nothing', and it's used for different scopes.
The void keyword can reference a void type, a reference to void, a void expression, a void operand or a void function. It also explicitly defines a function having no parameters.
Let's have a look at some of them.
The void type
First of all void object exists and have some special properties, as stated in ISO/IEC 9899:2017, §6.2.5 Types:
The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.
Pointers
The more useful reference to void, or void *, is a reference to an incomplete type, but itself is well defined, and then is a complete type, have a size, and can be used as any other standard variable as stated in ISO/IEC 9899:2017, §6.2.5 Types:
A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.
Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.
All pointers to structure types shall have the same representation and alignment requirements as each other.
All pointers to union types shall have the same representation and alignment requirements as each other.
Pointers to other types need not have the same representation or alignment requirements.
Casting to void
It can be used as cast to nullify an expression, but allowing the completion of any side effect of such expression. This concept is explained in the standard at ISO/IEC 9899:2017, §6.3 Conversions, §6.3.2.2 void:
The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression.
If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)
A practical example for the casting to void is its use to prevent warning for unused parameters in function definition:
int fn(int a, int b)
{
(void)b; //This will flag the parameter b as used
... //Your code is here
return 0;
}
The snippet above shows the standard practice used to mute compiler warnings. The cast to void of parameter b acts as an effective expression that don't generate code and marks b as used preventing compiler complains.
void Functions
The paragraph §6.3.2.2 void of the standard, covers also some explanation about void functions, that are such functions that don't return any value usable in an expression, but functions are called anyway to implement side effects.
void pointers properties
As we said before, pointers to void are much more useful because they allow to handle objects references in a generic way due to their property explained in ISO/IEC 9899:2017, §6.3.2.3 Pointers:
A pointer to void may be converted to or from a pointer to any object type.
A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
As practical example imagine a function returning a pointer to different objects depending on input parameters:
enum
{
FAMILY, //Software family as integer
VERSION, //Software version as float
NAME //Software release name as char string
} eRelease;
void *GetSoftwareInfo(eRelease par)
{
static const int iFamily = 1;
static const float fVersion = 2.0;
static const *char szName = "Rel2 Toaster";
switch(par)
{
case FAMILY:
return &iFamily;
case VERSION:
return &fVersion;
case NAME:
return szName;
}
return NULL;
}
In this snippet you can return a generic pointer that can be dependent on input par value.
void as functions parameter
The use of void parameter in functions definitions was introduced after the, so called, ANSI-Standard, to effectively disambiguate functions having variable number of arguments from functions having no arguments.
From standard ISO/IEC 9899:2017, 6.7.6.3 Function declarators (including prototypes):
The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
Actual compilers still support function declaration with empty parenthesis for backward compatibility, but this is an obsolete feature that will eventually be removed in future release of standard. See Future directions - §6.11.6 Function declarators:
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent
feature.
Consider the following example:
int foo(); //prototype of variable arguments function (backward compatibility)
int bar(void); //prototype of no arguments function
int a = foo(2); //Allowed
int b = foo(); //Allowed
int c = bar(); //Allowed
int d = bar(1); //Error!
Now resembling your test, if we call the function bar as follows:
int a = 1;
bar((void)a);
Triggers an error, because casting to void an object doesn't null it. So you are still trying to pass a void object as parameter to a function that don't have any.
Side effects
As requested this is a short explain for side effects concept.
A side effect is whichever alteration of objects and values derived from the execution of a statement, and which are not the direct expected effect.
int a = 0;
(void)b = ++a;
In the snippet above the void expression lose the direct effect, assigning b, but as side effect increase the value of a.
The only reference, explaining the meaning, in the standard can be found in 5.1.2.3 Program execution:
Accessing a volatile object, modifying an object, modifying a
file, or calling a function that does any of those operations are all
side effects, which are changes in the state of the execution
environment.
Evaluation of an expression in general includes both value
computations and initiation of side effects.
void is a type. Per C 2018 6.2.5 19, the type has no values (the set of values it can represent is empty), it is incomplete (its size is unknown), and it cannot be completed (its size cannot be known).
Regarding extern void a;, this does not define an object. It declares an identifier. If a were used in an expression (except as part of a sizeof or _Alignof operator), there would have to be a definition for it somewhere in the program. Since there cannot a definition of void object in strictly conforming C, a cannot be used in an expression. So I think this declaration is allowed in strictly conforming C but is not useful. It might be used in C implementations as an extension that allows getting the address of an object whose type is not known. (For example, define an actual object a in one module, then declare it as extern void a; in another module and use &a there to get its address.)
The declaration of functions with (void) as a parameter list is a kludge. Ideally, () might be used to indicate a function takes no parameters, as is the case in C++. However, due to the history of C, () was used to mean an unspecified parameter list, so something else had to be invented to mean no parameters. So (void) was adopted for that. Thus, (void) is an exception to the rules that would say (int) is for a function taking an int, (double) is for a function taking a double, and so on—(void) is a special case meaning that a function takes no parameters, not that it takes a void.
In foo((void) a), the cast does not make the value “not exist.” It converts a to the type void. The result is an expression of type void. That expression “exists,” but it has no value and cannot be used in an expression, so using it in foo((void) a) results in an error message.
From C Standard#6.2.5p19:
19 The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.
This indicate that the void type exists.
Doubt 1:
void foo(void); // This function takes no argument. Not the 'void' type.
Correct.
From C Standard#6.7.6.3p10 [emphasis mine]:
10 The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.
This is a special case they had to add to the language syntax because void foo(); already meant something different (void foo(); doesn't specify anything about foo's parameters). If it weren't for the old meaning of void foo();, void foo(); would have been the syntax to declare a no-argument function. You can't generalize anything from this. It's just a special case.
Doubt 2:
// Casting to 'void' should make the argument inexistant too...
foo((void)a);
No, it will not because void is also an object type though it is incomplete.
Doubt 3:
// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
Yes, it does exist and hence the compiler is reporting error on this statement.
Doubt 4:
// Am I not passing the 'void' type ?
foo(*p);
Declaration of foo() function:
void foo(void);
^^^^
The void in parameter list indicates that function will not take any argument because it has been declared with no parameters.
Just for reference, check this from C Standard#5.1.2.2.1p1 [emphasis mine]:
1 The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:
int main(void) { /* ... */ }
^^^^
Doubt 5:
extern void a; // Why is this authorised ???
This is authorized because void is a valid type and it is just a declaration. No storage will allocate to a.
In C, void can't be considered as a data type, it is a keyword used as a placeholder in place of a data type to show that actually there is no data. Hence this
void a;
is not valid.
while here
void foo(void);
void keyword is used to inform to the compiler that foo is not going to take any input argument nor it has return type.
In below case
int a = 42;
void *p;
a = *p; /* this causes error */
a = *p; is wrong because you can't dereference void pointer directly, you need to perform proper type casting first. for e.g
a = *(int*)p; /* first typecast and then do dereference */
Also this
foo(*p);
is wrong because of two reason,
firstly foo() doesn't expects any argument.
secondly you can't do *p as p is void pointer. Correct one is foo(*(int*)p); if foo() declaration is void foo(int);.
Note that this
(void)a;
doesn't do anything so your compiler might not giving any warning but when you do like
int b = (void)a;
compiler won't allow as void is not consider as data type.
Finally this
extern void a; // Why is this authorised ???
this is just a declaration not definition, a doesn't exist until you define it, since a is having extern storage class, you need to define somewhere & when you are going define like
a = 10;
compiler throws a error as
error: ‘a’ has an incomplete type
From C standard 6.2.5 Types
The void type comprises an empty set of values; it is an
incomplete object type that cannot be completed.
6.3.2.2 void
The (nonexistent) value of a void expression (an expression that has
type void) shall not be used in any way, and implicit or explicit
conversions (except to void) shall not be applied to such an
expression. If an expression of any other type is evaluated as a
void expression, its value or designator is discarded. (A void
expression is evaluated for its side effects.)
6.3.2.3 Pointers
A pointer to void may be converted to or from a pointer to any
object type. A pointer to any object type may be converted to a
pointer to void and back again; the result shall compare equal to the
original pointer.
A storage-class specifier or type qualifier modifies the keyword
void as a function parameter type list (6.7.6.3).
An attempt is made to use the value of a void expression, or an
implicit or explicit conversion (except to void) is applied to a
void expression (6.3.2.2).
First of all, it seems strange that you can declare a void object, meaning you just declare nothing.
void is an incomplete object type that cannot be completed. This mostly defines its uses in regular contexts, i.e. contexts that do not provide special treatment for void. Your extern declaration is one of such regular contexts. It is OK to use an incomplete data type in a non-defining declaration.
However, you will never be able to provide a matching definition for that declaration.
So the void in a prototype means nothing, and not the type void.
Correct. The parameter must be unnnamed. And the (void) combination is given special treatment: it is not one parameter of type void, but rather no parameters at all.
But then, I created a pointer to void, dereferenced it, and assigning the “result” to my int variable. I got the “incompatible type” error. That means the void type does exist here.
No. It is illegal to apply unary * operator to a void * pointer. Your code is invalid for that reason already. Your compiler issued a misleading diagnostic message. Formally, diagnostic messages are not required to properly describe the root of the problem. The compiler could've just said "Hi!".
Is void an actual type, or a keyword to means nothing ?
It is a type. It is an incomplete object type that cannot be completed.
I have got the following code:
struct student_info;
void paiming1(struct student_info student[]);
struct student_info
{
int num;
char name[6];
};
The IDE gives an error
error: array type has incomplete element type ‘struct student_info’
void paiming1(struct student_info student[]);
But if I use void paiming1(struct student_info *student); it works OK. Why is that? I am using GCC.
С language unconditionally requires array element type in all array declarations to be complete. Period.
6.7.6.2 Array declarators
Constraints
1 [...] The element type shall not be an incomplete or function
type. [...]
No exception is made for array declarations used in function parameter lists. This is different from C++ - the latter drops this completeness requirement for function parameter lists
struct S;
void foo(struct S[]); // OK in C++, invalid in C
Considering that in parameter list declaration type T [] is later adjusted to type T * anyway, this requirement might appear to be excessive. (And this is why C++ relaxed it.) Nevertheless, this restriction is present in C language. This is just one of the quirks of C language.
As you already know, you can explicitly switch to the equivalent
void paiming1(struct student_info *student);
form to work around the issue.
Careful reading of the standard makes it clear that in C99 and C11 the declaration is supposed to be a constraint violation. C11 6.7.2.6 Array declarations p1
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. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.
Since this contains references to the * that is valid only in function declarations that are not definitions, and nowhere else, the constraints as whole needs to be taken as applying to parameters.
For C90 the case is more complicated. This is actually discussed in C90 Defect Report 47 from 10 December, 1992.
2 of the 6 declarations given there are
/* 1. */ struct S;
/* 3. */ struct S *g(struct S a[]) { return a; }
and the defect report asks if these are strictly-conforming. It must be noted that unlike the question, these are prototypes that are part of definition rather than just declaration.
Nevertheless, the standard committee responds by saying that
The struct S is an incomplete type (subclause 6.5.2.3, page 62, lines 25-28). Also, an array of unknown size is an incomplete type (subclause 6.5.4.2, page 67, lines 9-10). Therefore, arrays of either of the above are not strictly conforming (subclause 6.1.2.5, page 23, lines 23-24). This makes declarations 3, 4, and 6 not strictly conforming. (But an implementation could get it right.)
As an aside, array parameters are adjusted to pointer type (subclause 6.7.1, page 82, lines 23-24). However, there is nothing to suggest that a not-strictly-conforming array type can magically be transformed into a strictly conforming pointer parameter via this rule.
The types in question can be interpreted two different ways. (Array to pointer conversion can happen as soon as possible or as late as possible.) Hence a program that uses such a form has undefined behavior.
(emphasis mine)
Since this has not been clarified in writing since 1992, we must agree that the behaviour is undefined and therefore the C standard imposes no requirements, and a compiler that successfully compiles this this can still conform to C90.
The committee also states that there is no constraint violation in C90, therefore a C90 conforming compiler need not output any diagnostics.
I've edited the answer; I previously claimed that this would apply to C99 and C11 alike, but the text was changed in C99 as above, therefore this is a constraint violation in C99, C11.
Compiler doesn't know anything about size of struct student_info until it is declared. This should work:
struct student_info
{
int num; //学号
char name[6]; //姓名
char sex[5]; //性别
char adress[20]; //家庭住址
char tel[11]; //电话
int chinese,math,english,huping,pingde,jiaoping,paiming1,paiming2;
double ave,zhongping;
};
void paiming1(struct student_info student[]);
void paiming2(struct student_info student[]);
When you declare it as a pointer using *, compiler knows the size of the argument (its an address).
Incomplete array types are used in the famous Struct hack and they are allowed since c99 standard. prior to c99 standard these were not allowed. I was looking at the standard and I am unable to conclude:
Are Incomplete array types allowed outside a structure?(All references I found in the standard C99: 6.7.2.1.15 talk about it as the last element in the structure).
So is the following program allowed to compile as per the standard?
int array[];
int main(){return 0;}
Second part of my questions is, If this is allowed is array guaranteed to be able to store atleast one element of they type int.
is the following program allowed to compile as per the standard?
Yes, as per:
(C99, 6.9.2p5) "EXAMPLE 2 If at the end of the translation unit containing
int i[];
the array i still has incomplete type, the implicit initializer
causes it to have one element, which is set to zero on program
startup."
So
int array[];
int main(){return 0;}
is valid and equivalent to:
int array[1];
int main(){return 0;}
Note that it is OK only if array has (like above) external linkage as:
(C99, 6.9.2p3) "If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type."
With reference to the question Where in a declaration may a storage class specifier be placed? I started analyzing the concept of declaration-specifiers and declarators. Following is the accumulation of my understanding.
Declarations
Generally, the C declarations follow the syntax of declaration-specifiers declarators;
declaration-specifiers comprises of type-specifiers , storage-class-specifiers and type-qualifiers
declarators can be variables,pointers,functions and arrays etc..
Rules that I assume
declaration-specifiers can be specified in any order, as an example
There cannot be more than a single storage-class-specifier
On the other hand there can be multiple type-qualifiers
storage-class-specifier shall not go with the declarator
Questions
Q1: In the declaration of a constant pointer, I see a mix of declarator and type-qualifier as below
const int *const ptr; //Need justification for the mix of declarator and type-specifier
Q2: There can be a pointer to static int. Is there a possibility of providing the pointer a static storage class? Means the pointer being static.
I'm not sure I full understand you first question. In terms of C++03 grammar const is a cv-qualifier. cv-qualifier can be present in decl-specifier-seq (as a specific kind of type-specifier), which is a "common" part of the declaration, as well as in init-declarator-list, which is a comma-separated sequence of individual declarators.
The grammar is specifically formulated that a const specifier belonging to an individual pointer declarator must follow the *. A const specifier that precedes the first * is not considered a part of the individual declarator. This means that in this example
int const *a, *b;
const belongs to the left-hand side: decl-specifier-seq, the "common" part of the declaration. I.e. both a and b are declared as int const *. Meanwhile this
int *a, const *b;
is simply ill-formed and won't compile.
Your second question doesn't look clear to me either. It seems that you got it backwards. You claim that "there can be a pointer to static int"? No, there's no way to declare such thing as "pointer to static int". You can declare a static pointer to int though
static int *p;
In this case the pointer itself is static, as you wanted it to be.
Q2: There can be a pointer to static int. Is there a possibility of providing the pointer a static storage class? Means the pointer being static.
Well, yes:
static T *a;
Declare a a pointer to T. a has static storage duration.
Generally, the C "declarations" is like this declaration-specifiers declarators;
Here, "declaration-specifiers" comprises of type-specifiers , storage-class-specifiers and type-qualifiers .
"declarators" can be variables,pointers,functions and arrays etc..
errors like :- [Error] expected declaration specifiers or '...' before string constant
This type of errors come ,reason for it is problem in declarations.