C
Table of Contents
- 1. C
- 1.1. static变量不要写在头文件中
- 1.2. void参数与空参数
- 1.3. switch与跳转表
- 1.4. volatile keyword in C
- 1.5. 栈对齐 stack align
- 1.6. gcc -E
- 1.7. 结构体的 struct A a={.a=1,.b=2};形式的初始化
- 1.8. do {} while (0)
- 1.9. char * str[MAXSIZE]
- 1.10. scanf
- 1.11. scanf中的%s与%[]
- 1.12. c中的声明与定义
- 1.13. compile time assert
- 1.14. C Preprocessor – the whole story
1. C
1.1. static变量不要写在头文件中
通常static变量在头文件中是错误的, 除非多个编译单元真的想各自使用独立的该变量
1.2. void参数与空参数
引用网址:http://david.tribble.com/text/cdiffs.htm#C99-func-vararg
“ Empty parameter lists
C distinguishes between a function declared with an empty parameter list and a function declared with a parameter list consisting of only void. The former is an unprototoped function taking an unspecified number of arguments, while the latter is a prototyped function taking no arguments.
// C code
extern int foo(); // Unspecified parameters extern int bar(void); // No parameters void baz() { foo(0); // Valid C, invalid C++ foo(1, 2); // Valid C, invalid C++ bar(); // Okay in both C and C++ bar(1); // Error in both C and C++ }
C++, on the other hand, makes no distinction between the two declarations and considers them both to mean a function taking no arguments.
// C++ code
extern int xyz(); extern int xyz(void); // Same as 'xyz()' in C++, // Different and invalid in C For code that is intended to be compiled as either C or C++, the best solution to this problem is to always declare functions taking no parameters with an explicit void prototype. For example: // Compiles as both C and C++ int bosho(void) { ... }
Empty function prototypes are a deprecated feature in C99 (as they were in C89).
即 在c中,void f()表示参数个数未指定,而不是表示无参数,表示无参数要使用void f(void) 在c++中,void f()和void f(void)是一样的.
即 尽量使用void f(void) 而不要使用void f()
– 发信人: sunway (sunway), 信区: CPP 标 题: Re: 关于函数参数个数的问题发信站: 北邮人论坛 (Wed Oct 14 13:01:02 2009), 站内
gcc确实能编译过去…神奇一般情况下不会错误,gcc默认使用cdecl调用约定,调用者会负责清理栈上的参数,但如果使用了stdcall,被调用的f不知道有参数要清理,而调用者又认为f会清理参数…然后就有问题了,比如:
void __attribute__((stdcall)) f (int a,int b) { f (1,2); } int main() { f(1,2); return 0; }
【 在 SuperBrother (xiaohui) 的大作中提到: 】
标 题: 关于函数参数个数的问题 发信站: 北邮人论坛 (Wed Oct 14 11:29:31 2009), 站内 void f() {} void g(int a) {} int main() { f(1); //g(1, 1); 编译不过,提示参数个数过多 return 0; } 用的是GCC 4.2.4 想问问为啥f(1)能过,而g(1, 1)不能? 另外,假设f()不用参数就能完成相应功能,调用f(1)会引起运行时错误吗? --
※ 修改:・sunway 于 Oct 14 13:02:34 修改本文・[FROM: 2001:da8:215:1800:211:11ff:
※ 来源:・北邮人论坛 http://forum.byr.edu.cn・[FROM: 2001:da8:215:5200:0:5efe:
1.3. switch与跳转表
int main (int argc, char * argv[]) { int a=0; switch (a) { case 1: printf ("%d\n",a); break; case 2: printf ("%d\n",a); break; case 3: printf ("%d\n",a); break; case 11: printf ("%d\n",a); break; case 100: printf ("%d\n",a); break; case 13: printf ("%d\n",a); break; case 4: printf ("%d\n",a); break; case 5: printf ("%d\n",a); break; case 6: printf ("%d\n",a); break; case 7: printf ("%d\n",a); break; case 8: printf ("%d\n",a); break; default: printf ("%d\n",a); break; } return 0; }
当case分支较少时,和if..else一样,通过一系列cmp,je..跳转. 当case分支较多时,会根据情况生成一个跳转表,如:
movl -24(%ebp), %edx movl .L14(,%edx,4), %eax jmp *%eax .section .rodata .align 4 .align 4 .L14: .long .L2 .long .L3 .long .L4 .long .L5 .long .L6 .long .L7 .long .L8 .long .L9 .long .L10 .long .L2
.L14就是跳转表的入口, %eax是分支在表中的偏移,如case为100,则其偏移量为100*4(因为每个表项为4字节) 这时存在一个问题是:如果case值范围过大,比如一共100个case,但有一个case的值为1000,则表中有900个项需要用default分支的地址去填充,浪费空间. 所以gcc在这种情况下又会转而使用原始的cmp,je..跳转.
1.4. volatile keyword in C
int main(int args, char **argv) { char x, y, z; int i; int a[16]; for(i=0; i<=16; i++) { a[i] = 0; printf("%d\n", i); } return 0; }
- 程序正常终止,打印出0-16
在 int i; 前加上 volatile, 死循环
Volatile is an ANSI C type modifier that is frequently needed in C code that is part of signal/interrupt handlers, threaded code, and other kernel code, including device drivers. In general, any data that may be undated asynchronously should be declared to be volatile. Incidentally, this issue is not related to CPU caches except that re-loading of variables into registers may involve cache hits or misses.
Why Use Volatile? The reason to use volatile is to insure that the compiler generates code to re-load a data item each time it is referenced in your program. Without volatile, the compiler may generate code that merely re-uses the value it already loaded into a register.
Volatile advises the compiler that the data may be modified in a manner that may not be determinable by the compiler. This could be, for example, when a pointer is mapped to a device's hardware registers. The device may independently change the values unbeknownst to the compiler.
With gcc the -O2 option is normally required to see the effect of not using volatile. Without -O2 or greater optimization, the compiler is likely to re-load registers each time a variable is referenced, anyway. Don't blame the optimizer if a program gets incorrect results because the program does not use volatile where required.
For example, if two threads share a variable, sum, and one or both threads modify it, then the other thread may use a stale value in a register instead of going back to memory to get the new value. Instead, each time the thread references sum, it must be re-loaded. The way to insure this occurs in ANSI C is to declare sum to be volatile.
Example: The use of volatile can be required to get correct answers. For example the program wrong will give incorrect results when it is compiled -O2 and without volatile. This slightly obtuse program is designed to stop after 100 ticks of an interval timer that ticks at 100Hz and print the value of the variable total. The tick count is incremented in the signal handler. When the count gets to 100, the program should terminate. If the tick count does not get to 100 within 10 seconds then an alarm goes off and the program terminates.
By compiling the program as: gcc -O2 -DVOLATILE=volatile wrong.c -o wrong_v you will see, (unless your program is preempted for quite a while), that the count gets to 100 and the program terminates as designed. With the program compiled as gcc -O2 wrong.c -o wrong_nv you will see, that the count becomes greater than 100 as shown when the handler prints it, but, the while loop does not terminate.
Incidentally, attempts to determine what is happening may thwart your efforts. For example, a function call, such as to printf(), or the use of a breakpoint, in the loop, will likely spill and re-load the registers.
http://en.allexperts.com/q/C-1587/volatile.htm http://www.netrino.com/node/80
1.5. 栈对齐 stack align
int main (int argc, char * argv[]) { int a=9; double b=0; char * c=&b; printf ("%d\n",*(int *)(c+8)); return 0; }
c+8后才是a的地址,而不是c+4
gcc保证,对于8 bytes的类型如double,在栈上是8字节对齐的
On the Pentium and subsequent x86 processors, there is a substantial performance penalty if double-precision variables are not stored 8-byte aligned; a factor of two or more is not unusual. Unfortunately, the stack (the place that local variables and subroutine arguments live) is not guaranteed by the Intel ABI to be 8-byte aligned.
Recent versions of gcc (as well as most other compilers, we are told, such as Intel's, Metrowerks', and Microsoft's) are able to keep the stack 8-byte aligned; gcc does this by default (see -mpreferred-stack-boundary in the gcc documentation). If you are not certain whether your compiler maintains stack alignment by default, it is a good idea to make sure.
Unfortunately, gcc only preserves the stack alignment―as a result, if the stack starts off misaligned, it will always be misaligned, with a disastrous effect on performance (in double precision).
1.6. gcc -E
1.7. 结构体的 struct A a={.a=1,.b=2};形式的初始化
gcc扩展:
struct A { int a; int b; }; int main(int argc, char *argv[]) { struct A a={.b=1,.a=2}; //或者 struct A a={2,1}; printf ("%d %d\n",a.a,a.b); return 0; }
1.8. do {} while (0)
1.9. char * str[MAXSIZE]
当打算将strlen是n的字符串存入str时,要分配n+1个char的空间,因为结尾的'\0'不包括在strlen里
1.10. scanf
1.10.1. %n
int a,b; scanf("%d%n",&a,&b) #>./a.out 1234 a=1234 b=4
1.10.2. %[]
1.10.3. %*
int a,b; scanf("%*d%n",&a); #>./a.out 1234 a=4
1.10.4. 空白字符的处理
空白字符包括 空格,tab,\n
int a,b; scanf("%d",&a); scanf("%d",&b); #>./a.out 1234 123 a=1234,b=123
实际上,第一个scanf执行后,输入流里还有一个\n,但是,下一句scanf用%d做参数时,会忽略输入流开头的空白字符. 大多数%..会忽略开头的空白字符,但以下几个除外: %c,%[] 所以:
int a,b; scanf("%d",&a); scanf("%c",&b); #>./a.out 1234 a=1234,b=134513674
b没有要求输入值,而是直接用的流中剩余的\n,对于%c,%[]这种情况,需要手工用%s忽略开头的空白字符,如:
int a,b; scanf("%d",&a); scanf("%*[ \t\n]%c",&b);
1.10.5. 回车的作用
回车用来表示将输入提交到输入流中,但回车本身也会被正常放在输入流中,所以 scanf("%d\n",&a) 也是可以被匹配的
1.10.6. scanf类型不匹配时死循环?
int a; label: scanf("%d",&a); goto label;
若输入不为整数,如'a',则后面的scanf会被跳过,死循环原因: scanf类型不匹配时,scanf失败,不匹配的数据仍然留在输入流中,必须用%*..丢弃这些错误的数据.
int a; label: scanf("%d",&a); scanf("%*s"); goto label;
1.11. scanf中的%s与%[]
scanf ("%s,%d",a,&b); 然后输入 abc,2,打印a的值是 abc,2 ,不是abc,就是说%s会贪婪的吞掉它后面所有的输入, 除非你输入空格让它停止
s
Matches a sequence of non-white-space characters; the next pointer must be
a pointer to character array that is long enough to hold the input sequence
and the terminating null character ('\0'), which is added automatically.
The input string stops at white space or at the maximum field width,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[ \t\n]+
whichever occurs first.
如果你确实喜欢用abc,2这种形式输入,可以试试这个: scanf("%[^,]%d",a,&b); 比较爽
我觉得%s可能就是%[^ \t\n]吧
1.12. c中的声明与定义
在1.c和2.c两个文件中,
以下情形时编译正确:
|----------------+----------------| | 1.c | 2.c | |----------------+----------------| | int a | int a | | int a | char a | | int a | extern int a | | int a | extern char a | | int a=2 | char a | | int a=2 | int a | | static int a=2 | static int a=1 | |----------------+----------------|
以下情形时出错:
|---------+----------| | 1.c | 2.c | |---------+----------| | int a=2 | int a=3 | | int a=2 | char a=3 | | int a=2 | int a=2 | |---------+----------|
结论: 没有赋值的定义如 int a 和 声明 extern int a 一样,可以声明多次,类型也可以不同(虽然这是个错误) 赋值的定义只能定义一次 static的定义和声明不干扰即:没赋值的可以随便写,赋值的只能写一次.extern关键字没有用.
1.13. compile time assert
#define ASSERT_STATIC(e) char UXXX[(e)-1]