c/c++ problem conclusion(to be continue!)
Recently,I have been involved in progrmming with C/C++ for over 6 years, during this period, some problems(or doubt) always flash in my mind repeatedly, so here, I want to list them from my notebook recursively. And this job will last in my future work life.
9/30/2014 4:24:03 PM @home
Timestamp:2013.04.01-2014.09.30
1. size of void struct?
sizeof(void struct)=1Byte
2. union
- All items share the same space
-
Only one item can be stored at the same time.
union StateMachine { char character; int num; char *str; double exp; };
note:used for compress space
3. internal and external data type
internal data type: int, char, long...
external data type: enmum, struct, union, class...
4. bit operation(right shift and left shift)
- left shift is easy to be understanded(add 0 at the right end)
-
right shift: logical and algorithm shift
logical: 0>>NUM(MSB-LSB)>> algorithm: MSB>>NUM(MSB-LSB)>> int i=-3; // fffffffd int j=i>>1; // fffffffe int k=i/2; /* j=-2; k=-1; note:negative number do the shift operation in the format of "补码" */
5. float type storage mechenism
6. const member function
Rules for const member function:
- Const object can call the const member function, and non const object can call all member function(including const member function)
- The member item of const object can not be modified, howerver, the pointer member is an exception.
- The const member function can not modify object’s data(no matter the object is const or not), and meanwhile, this rule will be check while the compilation process.
-
When the member item is prefied with
mutable
, it can be modified by any kind of method.class Stack { public: void Push(int elem); int Pop(void); int GetCount(void) const; private: int m_num; int m_data[100]; }; int Stack::GetCount(void) const { ++m_num;//error, try to modify member item; Pop(); //error, try to call non-const function; return m_num; }const **note**: The key word `const` must place at the end of const member function declaration sentence.
7. Memory allocation scheme of struct
-
Caculation of the size of the structure is not just add all structure items’ size, what’s more, the system allignmemt for the variable item should be took into consideration.
struct stu1 { int i; // shift address=0 char c; // shift address=4 int j; // shift address=8 };
-
Rules to follow(address allocation)
- The shift address of item must be integeral muliple of the item size;
- The size of structue must be integeral multiple of any item size;
8. printf
main()
{
int b=3;
int arr[]={6,7,9,9,10};
int *ptr=arr;
*(ptr++)+=123; //ptr 先取值,再自增
printf("%d,%d\n",*ptr,*(++ptr)); //output: 8, 8
} * Arguments of the printf function are pushed into the stack from right to left(running from right to left end)
Ref:2.p32.《程序员面试宝典》
9. File reading method
using C:
int main()
{
int fd, n;
char buf[102400];
fd=open("C:\\uuu.text");
if(-1==fd)
{
close(fd);
return -1;
}
n=read(fd,buf,sizeof(buf));
if((-1==n)||(0==n))
{
close(fd);
return -1;
}
*(buf+n)='\n';
close(fd);
return 0;
}
10.volatile
- usually, valatile shoule be used before changeable items(in order to avoid compilation opimization), valatile may be prefixed with the following items
- variable used in the interrupt service function
- flag shared by multiple tasks
- hardware register mapped by memory(value is changable during every time read/write)
note: What’s more, the data completion should also took into consideration, in the above circustumance, 1 can close the interrupt, 2 can forbidd the task switch, and 3 have to optimazate the hardware design.
11.static global variable, static local variable, static function
- 全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
- 从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
- static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
note:
-
static全局变量与普通的全局变量有什么区别:
static全局变量只初使化一次,防止在其他文件单元中被引用;
-
static局部变量和普通局部变量有什么区别:
static局部变量只被初始化一次,下一次依据上一次结果值;
-
static函数与普通函数有什么区别:
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
12. 函数可重入性
每个任务都单独维护自己的栈空间及其自身在内存寄存器中的值,
如果一个函数除了访问自己栈空间或是内核寄存器中的数据外,不
会访问其他任何数据,则这个函数被称为可重入的。
13. typedef & define
-
In the following examples, we will show the main differences between typedef and define
1. typedef char *pstr1; #define pstr2 char*; pstr1 s1,s2; //error, s2 is char type, not char* pointer pstr2 s3,s4; /* `typedef` create an new type, and `define` just do the string replace job*/ 2. #define f(x) x*x main() { int a=6, b=2, c; c=f(a)/f(b); printf("%d\n",c); //print 36, x*x/x*x } 3. typedef char *pstr; char string[4]="abc"; const char *p1=string; const pstr p2=string; p1++; p2++; /*error `pstr` is regarded as an new type, the key word `const` directly decrated `p2`, so `p2` cann't be modified. */
note:
- #define:
#ifdef
,#ifndef
and something like this can be used for logical judgement, and#undef
can cancel the definition; - typedef: It can restrict the range of decoration object(the variable or function just work at it’s definiton space).
14. Gloabal variable
int i;
i=1;
/*error, global variable can not be defined out of fucntion, the running sequence can not be guranteed. */
int i=1;
int main()
...
.h:
extern short x;
.c:
short x=0
/* the global variable declared in .h file , and must be defined in .c file(must prefixed with the type)*/
15. Header file
-
Go over how basic compilation of multiple files works.
If you have multiple files, the important thing is the difference between the declaration and definition of a function. The definition is probably what you are used to when defining functions: You write up the contents of the function, like
int square(int i) { return i*i; }
The declaration, on the other hand, lets you declare to the compiler that you know a function exists, but you don’t tell the compiler what it is. For example, you could write
int square(int i);
And the compiler would expect that the function “square” is defined elsewhere. Now, if you have two different files that you want to interoperate (for example, let’s say that the function “square” is defined in add.c, and you want to call square(10) in main.c), you need to do both a definition and a declaration.
First, you define square in add.c. Then, you declare it at the beginning of main.c. This let’s the compiler know when it is compiling main.c that there is a function “square” which is defined elsewhere. Now, you need to compile both main.c and add.c into object files. You can do this by calling
gcc -c main.c gcc -c add.c
This will produce the files main.o and add.o. They contain the compiled functions, but are not quite executable. The important thing to understand here is that main.o is “incomplete” in a sense. When compiling main.o, you told it that the function “square” exists, but the function “square” is not defined inside main.o. Thus main.o has a sort of “dangling reference” to the function “square”. It won’t compile into a full program unless you combine it with another .o (or a .so or .a) file which contains a definition of “square”. If you just try to link main.o into a program, i.e.
gcc -o executable main.o
You will get an error, because the compiler will try to resolve the dangling reference to the function “square”, but wont find any definition for it. However, if you include add.o when linking (linking is the process of resolving all these references to undefined functions while converting .o files to executables or .so files), then there won’t be any problem. i.e.
gcc -o executable main.o add.o
So that’s how to functionally use functions across C files, but stylistically, what I just showed you is “not the right way”. The only reason I did is because I think it will better help you understand what’s going on, rather than relying on “#include magic”. Now, you might have noticed before that things get a little messy if you have to redeclare every function you want to use at the top of main.c This is why often C programs use helper files called “headers” which have a .h extension. The idea of a header is that it contains just the declarations of the functions, without their definitions. This way, in order to compile a program using functions defined in add.c, you need not manually declare every function you are using, nor need you #include the entire add.c file in your code. Instead, you can
#include add.h
, which simply contains the declarations of all the functions of add.c.Now, a refresher on #include: #include simply copies the contents of one file directly into another. So, for example, the code
abc #include "wtf.txt" def is exactly equivalent to abc hello world def assuming that wtf.txt contains the text "hello world". So, if we put all the definitions of add.c in add.h (i.e.`int square(int i);`) and then at the top of main.c, we write `#include "add.h"`,This is functionally the same as if we had just manually declared the function "square" at the top of main.c. So the general idea of using headers is that you can have a special file that automatically declares all the functions you need by just #including it.
However, headers also have one more common use. Let’s suppose that main.c uses functions from 50 different files. The top of main.c would look like:
#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
…Instead, people often move all those
#includes
to themain.h
header file, and just#include main.h
from main.c. In this case, the header file serves two purposes. It declares the functions in main.c for use when included by other files, and it includes all of the dependencies of main.c when included from main.c. Using it this way also allows chains of dependencies. If you#include add.h
, not only do you get the functions defined in add.c, but you also implicitly get any functions which add.c uses, and any functions they use, and so on.Also, more subtly,
#including
a header file from it’s own .c file implicitly checks for errors you make. If for example, you accidentally defined square asdouble square(int i); in add.h, you normally might not realize until you were linking that main.o is looking for one definition of square, and add.o is providing another, incompatible one. This will cause you to get errors when linking, so you won't realize the mistake until later in the build process. However, if you `#include add.h` from add.c, to the compiler, your file looks like #include "add.h" int square(int i) { return i*i; } which after processing the #include statement will look like double square(int i); int square(int i) { return i*i; }
Which the compiler will notice when compiling add.c, and tell you about. Effectively, including your own header in this way prevents you from falsely advertising to other files the type of the functions you are providing.
-
Why you can use a function without ever declaring it
As you have noticed, in some cases you can actually use a function without every declaring it or
#including
any file which declares it. This is stupid, and everyone agrees that this is stupid. However, it is a legacy feature of the C programming language (and C compilers) that if you use a function without declaring it first, it just assumes that it is a function returning type “int”. So in effect, using a function is implicitly declaring that function as a function which returns “int” if it is not already declared. It’s very strange behavior if you think about it, and the compiler should warn you if you it doing that behavior. -
Header Guards
One other common practice is the use of “Header Guards”. To explain header guards, let’s look at a possible problem. Let’s say that we have two files: herp.c, and derp.c, and they both want to use functions contained in each other. Following the above guidelines, you might have a herp.h with the line
#include "derp.h" and a derp.h with the line #include "herp.h" Now, if you think about it, `#include "derp.h"` will be converted to the contents of derp.h, which in turn contains the line #include "herp.h", which will be converted to the contents of herp.h, and that contains... and so on, so the compiler will go on forever just expanding the includes. Similarly, if main.h `#includes` both herp.h and derp.h, and both herp.h and derp.h include add.h, we see that in main.h, we end up withtwo copies of add.h, one as a result of `#including` herp.h, and one as a result of including derp.h. So, the solution? A "Header guard", i.e. a piece of code which prevents any header from being `#included` twice. For add.h, for example, the normal way to do this is: #ifndef ADD_H #define ADD_H int sqrt(int i);... #endif This piece of code is essentially telling the preprocessor (the part of teh compiler which handles all of the `"#XXX"` statements) to check if `"ADD_H"` is already defined. If it isn't (ifndef) then it first defines `"ADD_H"` (in this context, ADD_H doesn't have to be defined as anything, it is just a boolean which is either defined or not), and then defines the rest of the contents of the header. However, if ADD_H is already defined, then `#including` this file will do nothing, because there is nothing outside of the `#ifndef` block. So the idea is that only the first time it is included in any given file will it actually add any text to that file. After that, `#including` it will not add any additional text to your file. ADD_H is just an arbitrary symbol you choose to keep track of whether add.h has been included yet. For every header, you use a different symbol to keep track of whether it has been included yet or not. For example, herp.h would probably use HERP_H instead of ADD_H. Using a `"header guard"` will fix any of the problems I listed above, where you have duplicate copies of a file included, or an infinite loop of `#includes`.
- array element initialization
For array element, element should be constant.