...menustart
- C Declaration
...menuend
-
declaration vs declarator
- A declarator is the part of a declaration that gives an object, type, or function its name and indicates whether an object is a pointer, or array.
-
roughly, a declarator is
- the identifier
- and any pointers,
- function brackets,
- or array indica-tions that go along with it
-
Here we also group any initializer here for convenience.
Name in C | Looks in C | How Many |
---|---|---|
pointer | * const volatile |
0 or many |
· | const,volatile 可选,位置可交换 | · |
direct_declarator | identifier |
exactly one |
· | identifier[optional_size]... |
· |
· | identifier(args...) |
· |
· | (declarator) |
· |
initializer | = initial_value |
0 or 1 |
- A declaration is made up of the parts shown below
- not all combinations are valid
- A declaration gives the basic underlying type of the variable and any initial value.
Name in C | Looks in C | How Many |
---|---|---|
type-specifier | void char short int long |
at least one type-specifier |
· | signed unsigned |
(not all combinations are valid) |
· | float double |
· |
· | struct_specifier |
· |
· | enum_specifier |
· |
· | union_specifier |
· |
storage-class | extern static register |
· |
· | auto typedef |
· |
type-qualifier | const volitile |
· |
---- | ------------------ | ---- |
declarator | definition above | exactly one |
more declarators | , declarator |
0 , or more |
semi-colon | ; |
1 |
- remember there are restrictions on legal declarations. You can't have any of these:
- a function can't return a function, so you'll never see
foo()()
- a function can't return an array, so you'll never see
foo()[]
- an array can't hold a function, so you'll never see
foo[]()
- a function can't return a function, so you'll never see
- You can have any of these:
- a function returning a pointer to a function is allowed:
int (* fun())()
- a function returning a pointer to an array is allowed:
int (* foo())[]
- an array holding pointers to functions is allowed:
int (*foo[])()
- an array can hold other arrays, so you'll frequently see
int foo[][]
- a function returning a pointer to a function is allowed:
and also look at enums.
- The syntax for structs is easy to remember:
- the usual way to group stuff together in C is to put it in braces:
{ stuff... }
- The keyword struct goes at the front so the compiler can distinguish it from a block:
struct {stuff... }
- the usual way to group stuff together in C is to put it in braces:
- The stuff in a struct can be any other data declarations:
- individual data items, arrays, other structs, pointers, and so on.
- We can follow a struct definition by some variable names, declaring variables of this struct type, for example:
struct {stuff... } plum, pomegranate, pear;
- "structure tag":
- The only other point to watch is that we can write an optional "structure tag" after the keyword
struct fruit_tag {stuff... } plum, pomegranate, pear;
- tag 的好处是,以后的声明可以 不再带
{stuff...}
struct fruit_tag
can now be used as a shorthand forstruct {stuff... }
, in future declarations.
- A struct thus has the general form:
struct optional_tag {
type_1 identifier_1;
type_2 identifier_2;
...
type_N identifier_N;
} optional_variable_definitions;
struct date_tag { short dd,mm,yy; } my_birthday, xmas;
struct date_tag easter, groundhog_day;
-
variables
my_birthday, xmas, easter, and groundhog_day
all have the identical type. -
Structs can also have bit fields, unnamed fields, and word-aligned fields.
- This is commonly used for "programming right down to the silicon," and you'll see it in systems programs.
- A bit field must have a type of int, unsigned int, or signed int (or a qualified version of one of these)
/* process ID info */
struct pid_tag {
unsigned int inactive :1;
unsigned int :1; /* 1 bit of padding */
unsigned int refcount :6;
unsigned int :0; /* pad to next word boundary
*/
short pid_id;
struct pid_tag *link;
};
- Finally there are two parameter passing issues associated with structs.
- when paramters are passed to a called function , parameters are passed in registers (for speed) where possible (并不是只会压进栈).
- Be aware that an int "i" may well be passed in a completely different manner to a struct "s" whose only member is an int.
- Assuming an int parameter is typically passed in a register, you may find that structs are instead passed on the stack.
- The second point to note is that by putting an array inside a struct like this:
/* array inside a struct */
struct s_tag { int a[100]; };
- you can now treat the array as a first-class type.
- You can copy the entire array with an assignment statement,
- pass it to a function by value,
- and make it the return type of a function.
- 数组终于可以做这些事情了。。。
struct s_tag { int a[100]; };
struct s_tag orange, lime, lemon;
struct s_tag twofold (struct s_tag s) {
int j;
for (j=0;j<100;j++) s.a[j] *= 2;
return s;
}
main() {
int i;
for (i=0;i<100;i++) lime.a[i] = 1;
lemon = twofold(lime);
orange = lemon; /* assigns entire struct */
}
- Let's finish up by showing one way to make a struct contain a pointer to its own type, as needed for lists, trees, and many dynamic data structures.
/* struct that points to the next struct */
struct node_tag { int datum;
struct node_tag *next;
};
struct node_tag a,b;
a.next = &b; /* example link-up */
a.next->next=NULL;
- 关键字:
- 成员起始地址, 最大成员字节长度
- 公式1:成员在结构体的起始地址,必须是 自身字节长度的整数倍,不是就补齐
- 公式2:整个Struct的长度 必须是最大成员字节长度 的整数倍
struct E1 {
int a;char b; char c
} e1;
- Example above
- 第一地址肯定存放a是4Byte地址
- 第二地址,b要1Byte的地址 -- 公式一登场: 4 == 1*N (N等于正整数) 答"是"!
- 地址现在为5Byte
- 下一个c要1Byte的地址同上 ,
- 地址现在为6Byte
- 公式二登场,在这个E1中最大的字节是4,而我们的地址字节是6,4的整数倍不是6,所以,要加2Byte(总地址),So,整个字节为8!
- CAUTION:
- 每个特定平台上的编译器都有自己的默认“对齐系数”。可以通过预编译命令#pragma pack(n)
struct E2 {
char b; int a ; char c
}
- E2 12字节长
- 背书式:
- 各成员变量存放的起始地址 相对于结构的起始地址 的偏移量必须为该变量的类型所占用的字节数的倍数
- 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间
- 同时按照上面的对齐方式调整位置 空缺的字节自动填充
- 同时为了确保结构的大小 为结构的字节边界数(即该结构中占用最大的空间的类型的字节数) 的倍数
- union have a similar appearance to structs, but the memory layout has one crucial difference
- Instead of each member being stored after the end of the previous one, all the members have an offset of zero.
- The storage for the individual members is thus overlaid: only one member at a time can be stored there.
- The good news is that unions have exactly the same general appearance as structs, but with the keyword
struct
replaced byunion
. - A union has the general form:
union optional_tag {
type_1 identifier_1;
type_2 identifier_2;
...
type_N identifier_N;
} optional_variable_definitions;
- Unions can also be used
- for one interpretation of two different pieces of data
- two different interpretations of the same data
// case 1
union secondary_characteristics {
char has_fur;
short num_of_legs_in_excess_of_4;
};
struct creature {
char has_backbone;
union secondary_characteristics form;
};
// case 2
union bits32_tag {
int whole; /* one 32-bit value */
struct {char c0,c1,c2,c3;} byte; /* four 8-bit bytes */
} value;
- This union allows a programmer to extract the full 32-bit value, or the individual byte fields
value.byte.c0
, and so on. - There are other ways to accomplish this, but the union does it without the need for extra assignments or type casting.
- Enums (enumerated types) are simply a way of associating a series of names with a series of integer values.
- In a weakly typed language like C, they provide very little that can't be done with a
#define
enum optional_tag {stuff... } optional_variable_definitions;
The stuff...
in this case is a list of identifiers, possibly with integer values assigned to them.
enum sizes { small=7, medium, large=10, humungous };
- The integer values start at zero by default.
- If you assign a value in the list, the next value is +1 greater, and so on.
- There is one advantage to enums:
- unlike
#defined
names which are typically discarded during compilation, - enum names usually persist through to the debugger, and can be used while debugging your code.
- unlike
- We have now reviewed the building blocks of declarations.
- The precedence rule for understanding C declarations is the one that the language lawyers like best.
- It's high on brevity, but very low on intuition.
// example
char* const *(*next)();
-
A
- 首先,变量名
next
, 注意到 它在括号里()
- 首先,变量名
-
B.1
- 所以我们把它与括号中的其他内容 组合起来看,
next
是一个指针,还不清楚 指向哪里
- 所以我们把它与括号中的其他内容 组合起来看,
-
B
- 从括号里出来, 现在我们有两个选择, 前缀
*
, 和 后缀()
- 从括号里出来, 现在我们有两个选择, 前缀
-
B.2
- B.2 告诉我们 后缀
()
优先级更高, 所以, "next is a pointer to a function returning...
- B.2 告诉我们 后缀
-
B.3
- 前缀
*
,一个指针
- 前缀
-
C
- 最后 ,
"char * const"
, a constant pointer to a character
- 最后 ,
-
整合起来
- next 是一个 指向函数的指针, 这个函数的返回值是 a pointer to a const pointer-to-char.
-
if you prefer something a little more intuitive, use
char *(*c[10])(int **p); // how 2 read ?
- c is an array[0..9] of pointer to a function returning a pointer-to-char
- Tool:
brew install cdecl
explain char *(*c[10])(int **);
- Typedefs are a funny kind of declaration:
- they introduce a new name for a type rather than reserving space for a variable.
- it doesn't introduce a new type, just a new name for a type , an alias
- If you refer back to the section on how a declaration is formed, you'll see that the
typedef
keyword can be part of a regular declaration, occurring somewhere near the beginning. - In fact, a typedef has exactly the same format as a variable declaration, only with this extra keyword to tip you off.
- the
typedef
keyword doesn't create a variable, but causes the declaration to say "this name is a synonym for the stated type." - 通常,typedef 用于涉及 指针的棘手 的情况。
- Example:
- The ANSI Standard shows that signal is declared as:
void (*signal(int sig, void (*func)(int)) ) (int);
- so signal is a function (with some funky arguments) returning a pointer to a function
- which function taking an int argument and returning void
- One of the funky arguments is itself:
void (*func)(int) ;
- is a pointer to a function , which taking an int argument and returning void.
- Here's how it can be simplified by a typedef :
typedef void (*ptr_to_func) (int);
/* this says that ptr_to_func is a pointer to a function
* that takes an int argument, and returns void
*/
ptr_to_func signal(int, ptr_to_func);
/* this says that signal is a function that takes
* two arguments, an int and a ptr_to_func, and
* returns a ptr_to_func
*/
- 注意区别:
cdecl> explain void (*signal ) (int);
declare signal as POINTER to function (int) returning void
cdecl> explain void (*signal() ) (int);
declare signal as FUNCTION returning pointer to function (int) returning void
- Typedef provides essentially nothing for structs, except the unhelpful ability to omit the struct keyword.
- 你不能再次修饰 typedef 'd typename
- You can extend a macro typename with other type specifiers ,
- but not a typedef 'd typename
#define peach int
unsigned peach i; /* works fine */
typedef int banana;
unsigned banana i; /* Bzzzt! illegal */
typedef struct my_tag {int i;} my_type;
struct my_tag variable_1;
my_type variable_2;
- typedef introduces the name
my_type
as a shorthand for "struct my_tag {int i}
" - it also introduces the structure tag
my_tag
that can equally be used with the keywordstruct
- If you use the same identifier for the type and the tag in a typedef, it has the effect of making the keyword "struct" optional
- which provides completely the wrong mental model for what is going on.
- So although these two declarations have a similar form, ery different things are happening
typedef struct fruit {int weight, price_per_lb } fruit; /* statement 1 */
struct veg {int weight, price_per_lb } veg; /* statement 2 */
- Statement 1 declares a structure tag "fruit" and a structure typedef "fruit" which can be used like this:
struct fruit mandarin; /* uses structure tag "fruit" */
fruit tangerine; /* uses structure type "fruit" */
- Statement 2 declares a structure tag "veg" and a variable veg. Only the structure tag can be used in further declarations, like this:
struct veg potato;
- Tips for Working with Typedefs
- Don't bother with
typedefs
forstructs
.
- Don't bother with
- Use typedefs for:
- types that combine arrays, structs, pointers, or functions.
- portable types.
- When you need a type that's at least (say) 20-bits, make it a typedef
- Then when you port the code to different platforms, select the right type, short, int, long, making the change in just the typedef, rather than in every declaration.
- casts.
- A typedef can provide a simple name for a complicated type cast. E.g.
typedef int (*ptr_to_int_fun)(void);
char * p = (ptr_to_int_fun) p;