從新認識C語言

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

wKioL1WYml_TvtP_AAAV2KT-Y2k468.gif

  根據上述代碼的分析,咱們發現這樣一個現象,在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空間,再接着申請上述兩個空間。執行結果是這樣的:

wKiom1WY1nqj7nPhAAATjUT_X0c763.gif

能夠看到兩次申請的test->str空間是同樣的,都是the address of (test->str[0]) = 0x4c0ed0。

而後,我在試驗第一次申請test->str空間後不釋放,在第二次申請中從新申請空間。代碼執行狀況以下:

wKioL1WY2Prif1HQAAASgIoQ-c8175.gif

能夠看到兩次申請的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;
}

但編譯的時候給了報了一堆錯誤:

wKiom1WY28vR8RIsAAAYQSRLUGs708.gif

其中第一句 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 的文章最後的附件下載。

相關文章
相關標籤/搜索