1.緣起數組
接觸C語言有三四年時間了,工做中也一直使用C語言。但對於一些C語言的特性和定義還存在一些疑問,這裏總結一下,做爲之後參考。函數
2.C語言的連接屬性測試
工做中無心發現了C語言一個有趣的問題,在兩個源文件中定義了同一個未初始化的變量,編譯器居然不報錯,可是若是在其中一個文件中定義並初始化,那就會報錯。我測試使用的代碼以下(測試環境window7(32位)gcc 4.5.0):編碼
main.c:spa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/* main.c */
#include <stdio.h>
char
G_TestValue;
int
G_TestValue2;
void
PrintTestValue(
void
);
int
main(
int
argc,
char
*argv[])
{
PrintTestValue();
printf
(
"main:\t\t(&G_TestValue)=0x%08x\n\t\t"
"(G_TestValue)=%d\n\t\tsizeof((G_TestValue))=%d\n"
"\t\tG_TestValue2=%d\n"
"\t\t&G_TestValue2=0x%08x\n"
,&(G_TestValue),G_TestValue,
sizeof
(G_TestValue),G_TestValue2,&G_TestValue2);
return
0;
}
|
global_test.c:指針
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* global_test.c*/
#include <stdio.h>
typedef
struct
test_struct_t{
char
a;
int
b;
} TestStruct_t;
TestStruct_t G_TestValue = {2,8};
int
G_TestValue2;
void
PrintTestValue(
void
)
{
printf
(
"PrintTestValue:\t(&G_TestValue)=0x%08x\n\t\t"
"(G_TestValue.a)=%d\n\t\tsizeof(G_TestValue)=%d\n"
"\t\tG_TestValue2=%d\n"
"\t\t&G_TestValue2=0x%08x\n"
,&(G_TestValue), G_TestValue.a,
sizeof
(G_TestValue),G_TestValue2,&G_TestValue2);
printf
(
"-----------------------------------------\n"
);
}
|
Makefile:code
1
2
3
4
5
6
7
8
9
10
|
test: global_test.o main.o
gcc -o test global_test.o main.o -std=gnu99
global_test.o: global_test.c
gcc -c global_test.c -std=gnu99
main.o: main.c
gcc -c main.c -std=gnu99
clean:
rm *.o test
|
代碼執行結果以下:blog
根據上述代碼的分析,咱們發現這樣一個現象,在main.c文件裏定義了char型變量G_TestValue但未初始化,在global_test.c文件裏定義告終構體類型的變量G_TestValue同時並初始化了,兩個文件裏都定義了同一個全局變量,一個未初始化,一個初始化了。編譯上述程序時並未報錯,運行結果發G_TestValue的地址是同樣的(本人實驗環境下是(&G_TestValue)=0x00402000),而且在main.c中打印出的G_TestValue的值爲2(即在global_test.c文件裏定義並初始化的值).這也就說明C語言連接器連接時,爲這個變量只分配了一個存儲空間,若是兩次定義同一個變量名稱的類型不同,以佔用空間大的那一個來分配空間。另外我使用G_TestValue2作了驗證,在兩個文件都定義了而且都沒有初始化,從打印結果看,他們是同一個地址。生命週期
爲何會這樣吶?內存
這涉及到C編譯器對多重定義的全局符號的解析和連接。在編譯階段,編譯器將全局符號信息隱含地編碼在可重定位目標文件的符號表裏。這裏有個「強符號(strong)」和「弱符號(weak)」的概念——前者指的是定義而且初始化了的變量,好比global_test.c裏的結構體G_TestValue,後者指的是未定義或者定義但未初始化的變量,好比main.c裏的整型G_TestValue和G_TestValue2,當符號被多重定義時,GNU連接器(ld)使用如下規則決議:
不容許出現多個相同強符號。
若是有一個強符號和多個弱符號,則選擇強符號。
若是有多個弱符號,那麼先決議到size最大的那個,若是一樣大小,則按照連接順序選擇第一個。
像上面這個例子中,全局變量G_TestValue存在重複定義。若是咱們將main.c中的b初始化賦值,那麼就存在兩個強符號而違反了規則一,編譯器報錯。若是知足規則二,則僅僅提出警告,實際運行時決議的是global_test.c中的強符號。而變量global_test2都是弱符號,因此只選擇一個(按照目標文件連接時的順序)。
關於C語言的連接屬性最權威的解釋是在ISO/IEC 9899 Programming languages — C(見文章附件)的6.2.2章節,有興趣的哥們能夠下載下來看看。另外推薦一篇關於C語言連接屬性描述不錯的文章:C語言中標識符的做用域、命名空間、連接屬性、生命週期、存儲類型。
具體關於linker相關的知識,比較系統的能夠參閱:linker and loader。
3.malloc的申請空間、及釋放空間
有一天上班,想到這樣一個問題:定義了一個結構體類型,成員變量有一個指針行變量。定義一個結構體類型的變量,爲其分配空間,而後再給成員變量分配空間,釋放結構體變量時,是否釋放告終構體成員變量申請的空間?仍是須要單獨進行釋放?
爲了測試這種狀況,寫了代碼想驗證一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
/* main.c */
#include <stdio.h>
#include <stdlib.h>
typedef
struct
malloc_test_tag
{
char
*str;
int
n;
}MallocTest_t ,*P_MallocTest_t;
int
main(
int
argc,
char
*argv[])
{
P_MallocTest_t test,saved_test;
printf
(
"malloc for test\n"
);
test = (P_MallocTest_t)
malloc
(
sizeof
(MallocTest_t));
if
(NULL == test )
{
exit
(1);
}
printf
(
"the address of (test) = 0x%x\n"
,test);
saved_test = test;
printf
(
"malloc for test->str\n"
);
test->str = (
char
*)
malloc
(6);
if
(NULL == test->str)
{
exit
(1);
}
printf
(
"the address of (test->str[0]) = 0x%x\n"
,test->str);
printf
(
"-----------------------------------------\n"
);
printf
(
"free for test->str\n"
);
free
(test->str);
printf
(
"free for test\n"
);
free
(test);
test = NULL;
printf
(
"malloc for test again!!!\n"
);
test = (P_MallocTest_t)
malloc
(
sizeof
(MallocTest_t));
if
(NULL == test )
{
exit
(1);
}
printf
(
"the address of (test) = 0x%x\n"
,test);
printf
(
"malloc for test->str again!!!!\n"
);
test->str = (
char
*)
malloc
(6);
if
(NULL == test->str)
{
exit
(1);
}
printf
(
"the address of (test->str[0]) = 0x%x\n"
,test->str);
printf
(
"free for test->str 2\n"
);
free
(saved_test->str);
saved_test->str = NULL;
printf
(
"free for test 2\n"
);
free
(test);
test = NULL;
return
0;
}
|
測試思路是這樣,根據malloc基本原理,申請後接着再申請,申請的是上次釋放的空間。我先申請test所須要的結構體空間,再接着申請結構體成員變量test->str的空間,而後釋放test->str的空間,釋放test空間,再接着申請上述兩個空間。執行結果是這樣的:
能夠看到兩次申請的test->str空間是同樣的,都是the address of (test->str[0]) = 0x4c0ed0。
而後,我在試驗第一次申請test->str空間後不釋放,在第二次申請中從新申請空間。代碼執行狀況以下:
能夠看到兩次申請的test->str空間是不同的,一個是the address of (test->str[0]) = 0x4c0ed0,一個是the address of (test->str[0]) = 0x570ee0。
從上面的實驗咱們能夠看到,使用malloc、free管理內存空間時,malloc和free爲基本單元的,你使用malloc申請多大的空間,就應該在使用完畢後,free多大的空間。向上面的例子中,你在結構體內又申請的空間,須要單獨釋放。我的猜測,malloc和free的基本實現:系統管理着一段內存空間(堆),你使用malloc申請的時候,系統會記住你申請空間首地址,你申請的大小等信息,當你free時會根據你要釋放的內存地址,而後查詢你申請空間時系統記錄的大小等其餘信息,進行釋放操做。所以,你釋放malloc申請的空間時,傳的地址參數應該是你申請空間獲得的那個地址,不然free函數可能執行失敗。
4.C語言變長數組
C99規範裏規定了可使用變長數組,只是知道,但實際項目中沒用過。我寫了如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/* main.c */
#include <stdio.h>
#include <stdlib.h>
int
n = 10;
int
test_array[n] = {1,2,3,4,5,};
int
main(
int
argc,
char
*argv[])
{
printf
(
"the value test_array[0] is %d\n"
,test_array[0]);
return
0;
}
|
但編譯的時候給了報了一堆錯誤:
其中第一句 error:定義了一個變長數組在文件做用域。咦,奇怪,C99不是支持定義變長數組嗎?個人編譯器是指定使用C99標準編譯的呀!
後來我將
int n = 10;
int test_array[n] = {1,2,3,4,5,};
挪到了函數內部,就沒有錯誤了。後來查閱ISO/IEC 9899 Programming languages — C得知,C99不支持文件做用域的變長數組定義和使用。而且變長數組不支持初始化。只能定義好變量後賦值。瞭然了以後,作了如下測試:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/* main.c */
#include <stdio.h>
#include <stdlib.h>
void
test(
int
n)
{
char
test_array[n];
printf
(
"the size of test_array is %d\n"
,
sizeof
(test_array));
}
int
main(
int
argc,
char
*argv[])
{
int
n = 10;
int
test_array[n];
test_array[0] = 10;
printf
(
"the value test_array[0] is %d\n"
,test_array[0]);
test(4);
test(5);
return
0;
}
|
看來C99支持的這個變長數組,仍是挺有用的哈。
5.C語言長語句分割、換行
寫代碼時一個語句太長,C有用支持直接分割嗎?
1
2
3
4
5
|
printf
(
"PrintTestValue:\t(&G_TestValue)=0x%08x\n\t\t"
"(G_TestValue.a)=%d\n\t\tsizeof(G_TestValue)=%d\n"
"\t\tG_TestValue2=%d\n"
"\t\t&G_TestValue2=0x%08x\n"
,&(G_TestValue), G_TestValue.a,
sizeof
(G_TestValue),G_TestValue2,&G_TestValue2);
|
C語言的語句分割符號是分號,空格和換行會被解析器忽略掉,因此通常語句能夠分開多行書寫。
像上面的printf函數,前面的格式化語句須要在每行都加上雙引號。
還有宏定義時比較特殊:
1
2
3
4
5
6
7
|
#define SAFE_DELETE(p) \
do
\
{ \
delete
p; \
p = NULL; \
} \
while
(0)
|
須要使用‘\’分隔符對語句進行換行。
--------------------------------------------------------------------------------------------------------
文章相關資源下載請到51CTO http://1801179.blog.51cto.com/1791179/1671043 的文章最後的附件下載。