~Linux C_1_一些细节

Outline 

  1. 打开Wall警告
  2. sizeof(数据类型)
  3. math.h
  4. 函数调用的本质
  5. 语句块
  6. 快速跳转
  7. struct初始化
  8. 数据段的分配
  9. string
  10. Python调用 .so
  11. 编译链接问题

  

 

 

====打开Wall警告====

一个好的习惯是打开gcc的-Wall选项,也就是让gcc提示所有的警告信息,不管是严重的还是不严重的,然后把这些问题从代码中全部消灭。比如把上例中的printf("Hello, world.\n");改成printf(0);然后编译运行:

$ gcc main.c
$ ./a.out

编译既不报错也不报警告,一切正常,但是运行程序什么也不打印。如果打开-Wall选项编译就会报警告了:

$ gcc -Wall main.c
main.c: In function ‘main’:
main.c:7: warning: null argument where non-null required (argument 1)

如果printf中的0是你不小心写上去的(例如错误地使用了编辑器的查找替换功能),这个警告就能帮助你发现错误。强烈建议你写每一个编译命令时都加上-Wall选项。



====转移符号====

表 2.1. C标准规定的转义字符

\' 单引号'(Single Quote或Apostrophe)
\" 双引号"
\? 问号?(Question Mark)
\\ 反斜线\(Backslash)
\a 响铃(Alert或Bell)
\b 退格(Backspace)
\f 分页符(Form Feed)
\n 换行(Line Feed)
\r 回车(Carriage Return)
\t 水平制表符(Horizontal Tab)
\v 垂直制表符(Vertical Tab)



注解:
  \f是分页符,主要用于控制打印机在打印源代码时提前分页,这样可以避免一个函数跨两页打印。
  用老式打字机打完一行之后需要这么两个动作,\r\n,所以现在Windows上的文本文件用\r\n做行分隔符,许多应用层网络协议(如HTTP)也用\r\n做行分隔符,而Linux和各种UNIX上的文本文件只用\n做行分隔符。



====sizeof(数据类型)====

rhel5.5版本:

short: 2
int: 4
long: 4
char: 1
float: 4
double:8
long double: 12short: 2
int: 4
long: 4
char: 1
float: 4
double:8
long double: 12



====
警告:初始化是一种特殊的声明,而不是一种赋值语句。
====



====math.h====

gcc main.c -lm

使用math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),
-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。
本书用到的大部分库函数(例如printf)位于libc.so库文件中,使用libc.so中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc的默认选项。


C标准库和glibc

  C 标准主要由两部分组成,一部分描述C的语法,另一部分描述C标准库。C标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义。要在一个平台上支持C语言,不仅要实现C编译器,还要实现C标准库,这样的实现才算符合C标准。不符合C标准的实现也是存在的,例如很多单片机的C 语言开发工具中只有C编译器而没有完整的C标准库。

  在Linux平台上最广泛使用的C函数库是glibc,其中包括C标准库的实现,也包括本书第三部分介绍的所有系统函数。几乎所有C程序都要调用glibc的库函数,所以glibc是Linux平台C程序运行的基础。glibc提供一组头文件和一组库文件,最基本、最常用的C标准库函数和系统函数在libc.so库文件中,几乎所有C程序的运行都依赖于libc.so,有些做数学计算的C程序依赖于libm.so,以后我们还会看到多线程的C程序依赖于libpthread.so。以后我说libc时专指libc.so这个库文件,而说glibc时指的是glibc提供的所有库文件。

  glibc并不是Linux平台唯一的基础C函数库,也有人在开发别的C函数库,比如适用于嵌入式系统的uClibc。



====
注意:$?是Shell中的一个特殊变量,表示上一条命令的退出状态。
====



====
提示:函数的返回值 加入运算中是可以的: c = a + func_int()
====



====
注意:局部变量可以用表达式初始化,但全局变量不行,全局变量在程序运行开始的时候就要得到一个确切的常量,所处于的数据端决定的。
====




====函数调用的本质====

#include<stdio.h>

void foo (void)
{
  int i;
  printf("%d\n", i);
  i = 777;
}

int main(void)
{
  foo();
  #if 1
  printf("hello\n"); //有无此句对函数结果的影响,分析!
  #endif
  foo();
  return 0;
}

 




====语句块====

语句块中也可以定义局部变量,例如:

void foo(void)
{
    int i = 0;
    {
        int i = 1;
        int j = 2;
        printf("i=%d, j=%d\n", i, j);
    }
    printf("i=%d\n", i); /* cannot access j here */
}

和函数的局部变量同样道理,每次进入语句块时为变量j分配存储空间,每次退出语句块时释放变量j的存储空间。语句块也构成一个作用域,和例 3.6 “作用域”的分析类似,如果整个源文件是一张大纸,foo函数是盖在上面的一张小纸,则函数中的语句块是盖在小纸上面的一张更小的纸。语句块中的变量i和函数的局部变量i是两个不同的变量,因此两次打印的i值是不同的;语句块中的变量j在退出语句块之后就没有了,因此最后一行的printf不能打印变量j,否则编译器会报错。
语句块可以用在任何允许出现语句的地方,不一定非得用在if语句中,单独使用语句块通常是为了定义一些比函数的局部变量更“局部”的变量。




====快速跳转====

goto

goto语句过于强大了,从程序中的任何地方都可以无条件跳转到任何其它地方,只要在那个地方定义一个标号就行,

唯一的限制是goto只能跳转到同一个函数中的某个标号处,而不能跳到别的函数中。

setjmp & longjmp

C标准库函数setjmp和longjmp配合起来可以实现函数间的跳转,但只能从被调用的函数跳回到它的直接或间接调用者(同时从栈空间弹出一个或多个栈帧),而不能从一个函数跳转到另一个和它毫不相干的函数中。setjmp/longjmp函数主要也是用于出错处理,比如函数A调用函数B,函数B调用函数C,如果在C中出现某个错误条件,使得函数B和C继续执行下去都没有意义了,可以利用setjmp/longjmp机制快速返回到函数A做出错处理。




====struct初始化====

struct segment {
  struct complex_struct start;
  struct complex_struct end;
};

从第 1 节 “复合类型与结构体”讲的Initializer的语法可以看出,Initializer也可以嵌套,因此嵌套结构体可以嵌套地初始化,例如:

// 以下这些初始化方式自动与声明对应关系

struct segment s = {{ 1.0, 2.0 }, { 4.0, 6.0 }};

也可以平坦(Flat)地初始化。例如:

struct segment s = { 1.0, 2.0, 4.0, 6.0 };

甚至可以把两种方式混合使用(这样可读性很差,应该避免):

struct segment s = {{ 1.0, 2.0 }, 4.0, 6.0 };

利用C99的新特性也可以做Memberwise Initialization,例如[15]:

struct segment s = { .start.x = 1.0, .end.x = 2.0 }; //部分初始化,C99的新特征




====对齐原则====

一、
对齐原则在于struct中包含了不同的数据类型。

实例环境:gcc-4.3.2

struct char_9 {
  char a1;
  char a2;
  ...
  char a9;
}

printf("%d\n", sizeof(struct char_9));

结果:9

// 全都是char,所以不涉及到对齐原则。


二、
struct complex_int_char {
  int b1;
  char b2;
}

result: 8

// 包含多种类型,遵循到对齐原则。


三、
struct size1 {
  int a;
  int b;
  long double c;


result: 20

// 看来对齐原则只遵循“四对齐”即可。




====数据段的分配====

实例:
int a;
int array[4];
struct hao_t {
  int a;
  int b;
} li_t = {10,20};


空间分配:
&array[4] = bf84de40 &a = bf84de40
&array[3] = bf84de3c
&array[2] = bf84de38
&array[1] = bf84de34
&array[0] = bf84de30

&jie_t.b = bf84de2c
&jie_t.a = bf84de28 &jie_t = bf84de28

//总结:从上向下分配,遵从小端方式。




====
警告:写代码时应尽可能避免硬编码!
====




====string====

注意每个字符末尾都有一个字符'\0'做结束符,这里的\0是ASCII码的八进制表示,也就是ASCII码为0的Null字符,在C语言中这种字符串也称为以零结尾的字符串(Null-terminated String)。数组元素可以通过数组名加下标的方式访问,而字符串字面值也可以像数组名一样使用,可以加下标访问其中的字符:

char c = "Hello, world.\n"[0]; //这种手法,貌似很拽!


但是通过下标修改其中的字符却是不允许的:

"Hello, world.\n"[0] = 'A';

这行代码会产生编译错误,说字符串字面值是只读的,不允许修改。



****字符串初始化数组****

如果用于初始化的字符串字面值比数组还长,比如:

char str[10] = "Hello, world.\n";

则数组str只包含字符串的前10个字符,不包含Null字符,这种情况编译器会给出警告。如果要用一个字符串字面值准确地初始化一个字符数组,最好的办法是不指定数组的长度,让编译器自己计算:

char str[] = "Hello, world.\n";

字符串字面值的长度包括Null字符在内一共15个字符,编译器会确定数组str的长度为15。

有一种情况需要特别注意,如果用于初始化的字符串字面值比数组刚好长出一个Null字符的长度,比如:

char str[14] = "Hello, world.\n";

则数组str不包含Null字符,并且编译器不会给出警告,[C99 Rationale]说这样规定是为程序员方便,以前的很多编译器都是这样实现的,不管它有理没理,C标准既然这么规定了我们也没办法,只能自己小心了。

实例:

char c = '@';

char arr[] = "hello, world";

char str0[4] = "012";       //自动加了null

char str1[4] = "0123";      //因为字符串长度正好,所以没哟自动加 '\0' ,这个要注意。

char str2[4] = "0123456";   //舍去了多余的初始化,当然也没有自动加 '\0'。

result:

@ d l r o w , o l l e h 2 1 0 3 2 1 0 3 2 1 0 

 




====Multi-dimensional Array====

注意,除了第一维的长度可以由编译器自动计算而不需要指定,其余各维都必须明确指定长度。


利用C99的新特性也可以做Memberwise Initialization,例如:

int a[3][2] = { [0][1] = 9, [2][1] = 8 };

//c99的新特性中看来是增加了“可局部初始化”的性能


http://hi.baidu.com/kebey2004/item/de34b73334773a342e20c4a4

 

 


 

Python调用 .so 

From: Python调用Linux下的动态库(.so)

(1) 生成.so:.c to .so

lolo@-id:workme$ gcc -Wall -g -fPIC -c linuxany.c -o linuxany.o
lolo@
-id:workme$ ls linux linuxany.c linuxany.o lolo@-id:workme$ gcc -shared linuxany.o -o linuxany.so
lolo@
-id:workme$ ls libmax.so linux linuxany.c linuxany.o linuxany.so

(2) 调用.so:Python call .so

#!/usr/bin/python
 
from ctypes import *
import os 
//参数为生成的.so文件所在的绝对路径
libtest = cdll.LoadLibrary(os.getcwd() + '/linuxany.so') 
//直接用方法名进行调用
print 
libtest.display('Hello,I am linuxany.com') 
print libtest.add(2,2010)

(3) 可能遇到的问题:

version `GLIBC_2.27' not found

Download updated version from: https://mirror.freedif.org/GNU/libc/ 

 

 

编译链接问题

relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC 

编译与链接的问题 gcc -fPIC -shared

 

 

 

Ref: 程序内存布局 PPT(系统概览,亦是面试常见问题)


Linux 内核的通用链表函数库

  /* implement */

 

应用位域的例子 - 段描述符

Ref: C语言中结构体的位域(bit-fields)

Ref: 结构体对齐(图解)与位域

 

Linux的虚拟内存管理

用户地址空间??

内核地址空间??

 

main() 不是第一个函数

f()赋值给g的时候还是要先执行一下子的,然后再执行main。

#include <stdio.h>

int f(), g = f();

int main()
{

}

int f()
{
    printf("Hello world!\n");
    return 0;
}

 

执行顺序是:先构造,再main,最后析构。

#include <stdio.h>

static __attribute__((constructor)) void before() {
    printf("Hello\n");
}

static __attribute__((destructor)) void after() {
    printf("World\n");
}


int main()
{
    printf("main()\n");
    return 0;
}

  

钩子函数:atexit():  

#include <stdio.h>
#include <stdlib.h>

void f(void) {
    printf("Bye! World 2\n");
}

void g(void) {
    printf("Bye! World 1.\n");
}

void y(void) {
    printf("Bye! World 3.\n");
}


int main()
{
    atexit(y);
    atexit(g);
    printf("main()\n");
    atexit(f);
    return 0;
}

OUTPUT: 

main()
Bye! World 2
Bye! World 1.
Bye! World 3.

 

 

posted @ 2012-11-13 17:45  郝壹贰叁  阅读(2292)  评论(0编辑  收藏  举报