从新认识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 的文章最后的附件下载。

相关文章
相关标签/搜索