昨天遇到一个怪问题。
我的本上原来装VS2005时,安装了VS自带的SQLExpres版本,用.net访问时很正常。昨天准备用java程序来访问之,装了一个jdbc,打开TCP/IP支持,创建用户。连接结果出错
该用户与可信 SQL Server 连接无关联。 (Microsoft SQL Server, Error: 18452)
尝试了无数遍,包括关闭windows防火墙,打开named pipe,都不好使。发现用SQLExpress的管理器也不能连接,也是该错误。
折腾了2小时无果,去微软的网站下了SQL server Express Sp2版本。安装过程中发现VS2005原来安装的实例设置的是Windows集成登录,而我创建的帐号是SQL认证登录。改成混合认证,一切正常了。
sttony's blog
2012年2月18日 星期六
吃椰子
非典型性C语言教程-1.1 变量
C语言最本质的东西就是函数和变量。 函数和变量在编译完成后会有实际的数据在那里,也就是运行时载入内存的时候会占用内存。现在先说变量。
变量按其存储在内存中的位置分有3种, 全局/静态的, 局部/栈(stack)的,和堆(heap)变量。这3个概念其实牵扯到OS对于进程内存的管理方式。
现 代的OS对于一个进程一般采用线性的内存,即对于32位系统而言一个进程的地址空间一般是从0x00000000开始一直到0xFFFFFFFF,很早在 8086机器上引入的分段模式已经不在使用了,数据和代码都放在一起。代码就对应于C语言的函数,全局静态的数据对应与全局/静态变量。除此之外OS载入 进程后还会动态的生成两种内存结构:一种是栈stack,一种是堆heap。由于有时stack的中文翻译也写成 堆栈,所以后面一律使用stack和heap来描述两者。Stack用于函数调用和返回,以及局部变量,heap对应与用malloc函数分配的内存空 间。
全局变量编译完成后在可执行文件中占用一个段,一般称为.data段。进程载入的时候,全局变量也跟着载入内 存,在内存中占用固定的地址。所有使用全局变量的地方最后都变成对固定内存地址的引用。堆变量一般由指针应用,由malloc分配。这种变量一旦分配也对 应与内存中的固定地址,但是要记得分配了就要释放,否则就是著名的内存泄漏错误(memory leak)。如果你的程序在结束时不会因为消耗过多的内存而引发系统响应变慢这样的问题,那么不释放也没有关系,程序结束时,malloc分配的内存会由 OS释放掉。
稍微麻烦一点的是局部变量。局部变量是在函数内部定义的,函数被调用的时候会移动栈顶,形成函数这次执行需要的active frame。下面都以x86机器为例。如图在x86上sp寄存器指向栈顶
bp
一般用于引用栈的内部。一个函数调用一般是这样:首先把函数的参数压入堆栈,然后调用call,call指令会自动压入返回地址。call之后就转到函数
的代码了,函数的头几条指令一般都是移动Sp和BP在栈中开辟一块内存放函数需要的局部变量。然后后面对局部变量的引用都被编译成相对bp的地址,
[bp+10]这样的地址。这样处理局部变量的目的就是为了让函数可以重入。在单线程下,不可能有同时运行的代码调用同一个函数,所以重入可以等价与递
归,函数自己调用自己。
最早出现的高级语言Forturn是不支持递归的,当时Forturn对于局部变量的处理和全局变量一样,如果函数自己调用自己,就不能分清楚引用的局部变量到底是哪一个。后来改成了stack的形式。举个例子:
这是一个简单的计算阶乘的递归实现的例子。现在假设局部变量ret有固定的地址,那么递归层次中下一层的调用会修改ret的值,使得调用它的函数得不到正确的ret值。我们实际看一下VC8下这段程序产生的代码。
0x0012FBC7 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 01 00 00 00 f3 13 41 00 c8 fc 12 00 d6 13 41 00 01 00 00 00
0x0012FBF0 ac fd 12 00 9c f9 84 07 00 e0 fd 7f cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
此时ESP寄存器是0x0012FB9E, 表示栈顶, 栈的第一个元素是01 00 00 00, 即ret=1,后面的一个32位数是00 d6 13 41 即0x4113d600,这个是返回地址。后一个01 00 00 00表示的是函数的参数。调试的时候VC8在栈中插入了大量的cc, 这个是为了VC8的-GS选项,即栈检查,防止缓冲区溢出错误。
可以看到函数返回的时候有退栈的动作(004113E6 add esp,0CCh ),就是释放了局部变量所占用的栈空间,于是局部变量的生存就结束了。
写程序的时候使用那种变量要了解这种变量的特性,和生存周期。比如返回局部变量的地址就是一种典型的错误,局部变量占用的内存,在函数结束之后就还给系统了,返回局部变量的地址在函数完成之后使用,属于未定义的行为。
变量按其存储在内存中的位置分有3种, 全局/静态的, 局部/栈(stack)的,和堆(heap)变量。这3个概念其实牵扯到OS对于进程内存的管理方式。
现 代的OS对于一个进程一般采用线性的内存,即对于32位系统而言一个进程的地址空间一般是从0x00000000开始一直到0xFFFFFFFF,很早在 8086机器上引入的分段模式已经不在使用了,数据和代码都放在一起。代码就对应于C语言的函数,全局静态的数据对应与全局/静态变量。除此之外OS载入 进程后还会动态的生成两种内存结构:一种是栈stack,一种是堆heap。由于有时stack的中文翻译也写成 堆栈,所以后面一律使用stack和heap来描述两者。Stack用于函数调用和返回,以及局部变量,heap对应与用malloc函数分配的内存空 间。
全局变量编译完成后在可执行文件中占用一个段,一般称为.data段。进程载入的时候,全局变量也跟着载入内 存,在内存中占用固定的地址。所有使用全局变量的地方最后都变成对固定内存地址的引用。堆变量一般由指针应用,由malloc分配。这种变量一旦分配也对 应与内存中的固定地址,但是要记得分配了就要释放,否则就是著名的内存泄漏错误(memory leak)。如果你的程序在结束时不会因为消耗过多的内存而引发系统响应变慢这样的问题,那么不释放也没有关系,程序结束时,malloc分配的内存会由 OS释放掉。
稍微麻烦一点的是局部变量。局部变量是在函数内部定义的,函数被调用的时候会移动栈顶,形成函数这次执行需要的active frame。下面都以x86机器为例。如图在x86上sp寄存器指向栈顶
最早出现的高级语言Forturn是不支持递归的,当时Forturn对于局部变量的处理和全局变量一样,如果函数自己调用自己,就不能分清楚引用的局部变量到底是哪一个。后来改成了stack的形式。举个例子:
int ff(int n)
{
int ret=1;
if(n==0 || n==1)
ret=1;
else
{
ret=ff(n-1)*n;
}
return ret;
}
这是一个简单的计算阶乘的递归实现的例子。现在假设局部变量ret有固定的地址,那么递归层次中下一层的调用会修改ret的值,使得调用它的函数得不到正确的ret值。我们实际看一下VC8下这段程序产生的代码。
int ff(int n) { 00411390 push ebp 00411391 mov ebp,esp 00411393 sub esp,0CCh 00411399 push ebx 0041139A push esi 0041139B push edi 0041139C lea edi,[ebp-0CCh] 004113A2 mov ecx,33h 004113A7 mov eax,0CCCCCCCCh 004113AC rep stos dword ptr es:[edi] int ret=1; 004113AE mov dword ptr [ret],1 if(n==0 || n==1) 004113B5 cmp dword ptr [n],0 004113B9 je ff+31h (4113C1h) 004113BB cmp dword ptr [n],1 004113BF jne ff+3Ah (4113CAh) ret=1; 004113C1 mov dword ptr [ret],1 else 004113C8 jmp ff+50h (4113E0h) { ret=ff(n-1)*n; 004113CA mov eax,dword ptr [n] 004113CD sub eax,1 004113D0 push eax 004113D1 call ff (411145h) 004113D6 add esp,4 004113D9 imul eax,dword ptr [n] 004113DD mov dword ptr [ret],eax } return ret; 004113E0 mov eax,dword ptr [ret] } 004113E3 pop edi 004113E4 pop esi 004113E5 pop ebx 004113E6 add esp,0CCh 004113EC cmp ebp,esp 004113EE call @ILT+300(__RTC_CheckEsp) (411131h) 004113F3 mov esp,ebp 004113F5 pop ebp 004113F6 ret也 许你不知道如何在VC8下显示反汇编,后面会专门讨论VC8。VC8的反汇编代码已经做了优化,比如局部变量ret的地址表示成了[ret] (004113AE)实际上它应该是类似与[bp+xx]这样的地址。其次VC8的调试版本里面所有的没有初始化的变量都用0xcc来填充,而不是 0x00。 我个人猜想之所有用0xcc来填充是因为0xcc在x86机器上恰好是int 3h指令的机器码,而int 3h指令就是调试中断的指令。可能你不懂汇编,这里只简单的说说。函数调用的开始几行汇编代码都类似,就是完成了建立局部变量空间,也就是active frame的过程,主要操作的是sp和bp寄存器。看一看算ff(2)时的系统内存:
0x0012FBC7 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 01 00 00 00 f3 13 41 00 c8 fc 12 00 d6 13 41 00 01 00 00 00
0x0012FBF0 ac fd 12 00 9c f9 84 07 00 e0 fd 7f cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
此时ESP寄存器是0x0012FB9E, 表示栈顶, 栈的第一个元素是01 00 00 00, 即ret=1,后面的一个32位数是00 d6 13 41 即0x4113d600,这个是返回地址。后一个01 00 00 00表示的是函数的参数。调试的时候VC8在栈中插入了大量的cc, 这个是为了VC8的-GS选项,即栈检查,防止缓冲区溢出错误。
可以看到函数返回的时候有退栈的动作(004113E6 add esp,0CCh ),就是释放了局部变量所占用的栈空间,于是局部变量的生存就结束了。
写程序的时候使用那种变量要了解这种变量的特性,和生存周期。比如返回局部变量的地址就是一种典型的错误,局部变量占用的内存,在函数结束之后就还给系统了,返回局部变量的地址在函数完成之后使用,属于未定义的行为。
最近开始学习图像识别,第一步FFT
代码根据csdn论坛上一位达人的C++代码改编:
创建FFT对象的时候指定层数ex,也就是点数N= pow(2,ex); 构造函数中会计算omega(0到N-1)节约时间。
public class FFT {
/** Creates a new instance of FFT */
public FFT(int _ex) {
ex = _ex;
N = (int) Math.pow(2, ex);
omegaRe = new double[N];
omegaIm = new double[N];
for (int k = 0; k < N; k++) {
omegaRe[k] = Math.cos(2 * Math.PI / N * k);
omegaIm[k] = Math.sin(-2 * Math.PI / N * k);
}
}
double omegaRe[];
double omegaIm[];
int ex;
int N;
public int rev(int x) {
int in = x;
int ret = 0;
for (int i = 0; i < ex; i++) {
ret = ret | (in % 2 << ex - i - 1);
in = in >> 1;
}
return ret;
}
public void fft(double[] inputRe, double[] inputIm, double[] outputRe,
double[] outputIm) {
assert (inputRe.length >= N);
assert (inputRe.length == inputIm.length);
assert (outputRe.length == outputIm.length);
assert (inputRe.length == outputRe.length);
int k; // 当前树的深度
int j;
int nBtFlyLen = 0;
if (inputIm == null) {
inputIm = new double[inputRe.length];
}
// 变换需要的工作空间
double[] work1Re = new double[N];
double[] work1Im = new double[N];
double[] work2Re = new double[N];
double[] work2Im = new double[N];
// 临时变量
double[] tmpRe;
double[] tmpIm;
// 初始化,写入数据
System.arraycopy(inputRe, 0, work1Re, 0, N);
System.arraycopy(inputIm, 0, work1Im, 0, N);
// 临时变量
int nInter = 0;
// 蝶形算法进行快速傅立叶变换
for (k = 0; k < ex; k++) {
for (j = 0; j < (1 << k); j++) {
// 计算长度
nBtFlyLen = 1 << (ex - k);
// 倒序重排,加权计算
for (int i = 0; i < nBtFlyLen / 2; i++) {
nInter = j * nBtFlyLen;
work2Re[i + nInter] = work1Re[i + nInter]
+ work1Re[i + nInter + nBtFlyLen / 2];
work2Im[i + nInter] = work1Im[i + nInter]
+ work1Im[i + nInter + nBtFlyLen / 2];
double a = work1Re[i + nInter]
- work1Re[i + nInter + nBtFlyLen / 2];
double b = work1Im[i + nInter]
- work1Im[i + nInter + nBtFlyLen / 2];
double c = omegaRe[i * (1 << k)];
double d = omegaIm[i * (1 << k)];
work2Re[i + nInter + nBtFlyLen / 2] = a * c - b * d;
work2Im[i + nInter + nBtFlyLen / 2] = a * d + b * c;
}
}
// 交换 work1和work2的数据
tmpRe = work1Re;
tmpIm = work1Im;
work1Re = work2Re;
work1Im = work2Im;
work2Re = tmpRe;
work2Im = tmpIm;
}
// 重新排序
for (j = 0; j < N; j++) {
nInter = 0;
for (int i = 0; i < ex; i++) {
if ((j & (1 << i)) != 0) {
nInter += 1 << (ex - i - 1);
}
}
outputRe[j] = work1Re[nInter];
outputIm[j] = work1Im[nInter];
}
}
public void ifft(double[] inputRe, double[] inputIm, double[] outputRe,
double[] outputIm) {
assert (inputRe.length >= N);
assert (inputRe.length == inputIm.length);
assert (outputRe.length == outputIm.length);
assert (inputRe.length == outputRe.length);
double[] transformRe = new double[N];
double[] transformIm = new double[N];
for (int i = 0; i < N; i++) {
transformRe[i] = inputRe[i];
transformIm[i] = -inputIm[i];
}
fft(transformRe, transformIm, outputRe, outputIm);
for (int i = 0; i < N; i++) {
outputRe[i] = outputRe[i] / N;
outputIm[i] = -outputIm[i] / N;
}
}
public void fft_2d(double[][] inputRe, double[][] inputIm,
double[][] outputRe, double[][] outputIm) {
// 检查不强, 没有循环检查二维数组的第二维
assert (inputRe.length >= N);
assert (inputRe.length == inputIm.length);
assert (outputRe.length == outputIm.length);
assert (inputRe.length == outputRe.length);
if (inputIm == null)
inputIm = new double[N][N];
for (int i = 0; i < N; i++) {
fft(inputRe[i], inputIm[i], outputRe[i], outputIm[i]);
}
// 重新为in分配一块空间
double[][] inRe = new double[N][N];
double[][] inIm = new double[N][N];
// 转置
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
inRe[i][j] = outputRe[j][i];
inIm[i][j] = outputIm[j][i];
}
}
for (int i = 0; i < N; i++) {
fft(inRe[i], inIm[i], outputRe[i], outputIm[i]);
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
inRe[i][j] = outputRe[j][i];
inIm[i][j] = outputIm[j][i];
}
}
for (int i = 0; i < N; i++) {
System.arraycopy(inRe[i], 0, outputRe[i], 0, N);
System.arraycopy(inIm[i], 0, outputIm[i], 0, N);
}
}
public void ifft_2d(double[][] inputRe, double[][] inputIm,
double[][] outputRe, double[][] outputIm) {
double[][] workRe = new double[N][N];
double[][] workIm = new double[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
workRe[i][j] = inputRe[i][j];
workIm[i][j] = -inputIm[i][j];
}
}
fft_2d(workRe, workIm, outputRe, outputIm);
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
outputRe[i][j] = outputRe[i][j] / (N * N);
outputIm[i][j] = -outputIm[i][j] / (N * N);
}
}
}
}
创建FFT对象的时候指定层数ex,也就是点数N= pow(2,ex); 构造函数中会计算omega(0到N-1)节约时间。
非典型性C语言教程-1.0 翻译单元,标识符,内部连接,外部连接
前面讲了很多编译C语言文件的问题,现在开始讲C语言本身了。
第一个概念就是翻译单元(translate unit)。一个源文件就是一个翻译单元。前面说过#include指令只是将文件的内容插入到#include指令的位置,比如这样的代码也是可以的:
#include "mydoc.txt"或者#include "mycsource.c"但是这样写出的代码有点怪异,所以这里我们按照一般的概念,编译源文件(.c文件),#include头文件(.h文件)。确 切的说,一个翻译单元就是一个源文件和它include的头文件。一个翻译单元编译成一个目标文件,最后将多个翻译单元连接起来生成一个完整的可执行程 序。
第二个概念是标识符(identifier)。标识符包括,变量的名字,函数的名字,结构的名字,联合的名 字,typedef的名字,枚举中的名字,#define定义的宏也是标识符。和翻译单元的概念结合起来,标识符有两类,一类是internal linkage 一类是external linkage。
什么是内部连接(internal linkage )呢?就是编译成目标文件之后就不存在了,翻译单元的外面看不到内部连接。比如,一个局部变量,局部变量的可见性仅持续到函数返回。在比如一个结构的名字,多个翻译单元中可以有同名的结构。
外部连接(external linkage)则是编译成目标文件之后仍然存在的符号。比如一个全局变量的名字,比如一个普通函数的名字。多个翻译单元中不能有同名的函数或是全局变量,否则连接的时候会报符号重名错误。
0.4 中说了,目标文件会有一个符号表,内部连接和外部连接的本质区别就是:内部连接不在目标文件的符号表中,而外部连接在目标文件的符号表中。内部连接包含: 局部变量,结构名字,枚举名字,联合的名字,以及静态全局变量和静态函数。可能对不了解,后面会详细讲静态全局变量和静态函数。外部连接主要是全局变量和 函数。
给个例子,有一个程序由2个.c文件组成,看看那些会引起符号冲突那些不会。
头文件中最好只放有内部连接的东西,因为一个头文件可能被多个翻译单元所包含引用,如果在头文件中放个有外部连接的东西,很容易就在最后连接的时候出错。
带 external的 标识符是内部连接,比如,在第二个文件中的int g_A改成, external int g_A就没问题了。external是告诉编译器,我要使用在别的翻译单元中的东西,编译器就会留个占位符在那里,到连接的时候再解决这个符号。不使用就 不会有占位符,但是如果你使用了,但是最后连接的时候却没有这个实际的变量,那么编译器就会报错,unresolved符号。
函数的声明和external 类似。你可以写 int dd(int); 告诉编译器,在别的翻译单元中有这么一个函数,我将使用它,然后你就可以调用dd了只要符合int dd(int)就可以。同理,如果连接的时候找不到这个函数,编译器也会报告未解决的符号。
一 个典型的错误是为了写一个共用的函数,在.h文件中写一个函数体。然后在要使用这个函数的地方#include这个头文件。于是在多个翻译单元中都有了这 个函数,连接的时候报告重定义。正确的做法是讲函数的实际代码放在一个源文件中,讲函数的声明放在头文件里面。需要使用函数的地方#include 头文件,最后连接的时候把包含实际代码的源文件编译连接上。
第一个概念就是翻译单元(translate unit)。一个源文件就是一个翻译单元。前面说过#include指令只是将文件的内容插入到#include指令的位置,比如这样的代码也是可以的:
#include "mydoc.txt"或者#include "mycsource.c"但是这样写出的代码有点怪异,所以这里我们按照一般的概念,编译源文件(.c文件),#include头文件(.h文件)。确 切的说,一个翻译单元就是一个源文件和它include的头文件。一个翻译单元编译成一个目标文件,最后将多个翻译单元连接起来生成一个完整的可执行程 序。
第二个概念是标识符(identifier)。标识符包括,变量的名字,函数的名字,结构的名字,联合的名 字,typedef的名字,枚举中的名字,#define定义的宏也是标识符。和翻译单元的概念结合起来,标识符有两类,一类是internal linkage 一类是external linkage。
什么是内部连接(internal linkage )呢?就是编译成目标文件之后就不存在了,翻译单元的外面看不到内部连接。比如,一个局部变量,局部变量的可见性仅持续到函数返回。在比如一个结构的名字,多个翻译单元中可以有同名的结构。
外部连接(external linkage)则是编译成目标文件之后仍然存在的符号。比如一个全局变量的名字,比如一个普通函数的名字。多个翻译单元中不能有同名的函数或是全局变量,否则连接的时候会报符号重名错误。
0.4 中说了,目标文件会有一个符号表,内部连接和外部连接的本质区别就是:内部连接不在目标文件的符号表中,而外部连接在目标文件的符号表中。内部连接包含: 局部变量,结构名字,枚举名字,联合的名字,以及静态全局变量和静态函数。可能对不了解,后面会详细讲静态全局变量和静态函数。外部连接主要是全局变量和 函数。
给个例子,有一个程序由2个.c文件组成,看看那些会引起符号冲突那些不会。
// a.c
int dd(int)
{
return 20;
}
int g_A=0;
static g_A1=10;
struct ax
{
int a;
};
int main( void )
{
dd(1);
}
//b.c
static int dd(int)
{
return 20;
}
int g_A=0;
static g_A1=10;
struct ax
{
int a;
};
带static的函数和全局变量都是不会冲突的,但是去掉了,就肯定冲突,struct ax不会冲突,不带static的g_A会冲突。头文件中最好只放有内部连接的东西,因为一个头文件可能被多个翻译单元所包含引用,如果在头文件中放个有外部连接的东西,很容易就在最后连接的时候出错。
带 external的 标识符是内部连接,比如,在第二个文件中的int g_A改成, external int g_A就没问题了。external是告诉编译器,我要使用在别的翻译单元中的东西,编译器就会留个占位符在那里,到连接的时候再解决这个符号。不使用就 不会有占位符,但是如果你使用了,但是最后连接的时候却没有这个实际的变量,那么编译器就会报错,unresolved符号。
函数的声明和external 类似。你可以写 int dd(int); 告诉编译器,在别的翻译单元中有这么一个函数,我将使用它,然后你就可以调用dd了只要符合int dd(int)就可以。同理,如果连接的时候找不到这个函数,编译器也会报告未解决的符号。
一 个典型的错误是为了写一个共用的函数,在.h文件中写一个函数体。然后在要使用这个函数的地方#include这个头文件。于是在多个翻译单元中都有了这 个函数,连接的时候报告重定义。正确的做法是讲函数的实际代码放在一个源文件中,讲函数的声明放在头文件里面。需要使用函数的地方#include 头文件,最后连接的时候把包含实际代码的源文件编译连接上。
非典型性C语言教程- 0.4 连接
0.2 里面说过,当你使用gcc -o hello hello.c 时,gcc实际是先调用cpp预处理hello.c中的预处理命令,再自己编译之,最后调用ld进行连接生成可执行文件。Windows下是cl.exe 和link.exe。
用 -c选项可以让编译器不连接,如 gcc -c hello.c,或是cl -c hello.c 这样会只将源文件编译成目标文件。Unix下叫hello.o,Windows下叫hello.obj。目标文件是不能执行的,但是目标文件中已经是可以 执行的机器指令了。其实目标文件和最后生成的可执行文件一样,都是一组函数(函数就是一段一段的机器指令),两者的区别在于目标文件中对函数的调用都是按 名字调用的,而可执行程序中已经是按地址调用了。先举例子,比如hello.c 程序中调用了printf("hello.c");。编译成的目标文件中,会有一个符号表,其中有一个符号叫printf,并且表明他是一个函数。但是由 于这个函数实际在标准库中,所以还不知道这个函数的具体细节,只有符号在那里。Hello world例子中,自己写的只有一个源文件,编译之后只需要与C语言的标准库连接就可以。
C语言的标准库实际就是一 组函数。在Windows下你安装了VC或是VS会给你安装上,在%VCHOME%\lib目录下,叫msvcrt.lib。这个lib文件实际使用的 是%Windows%\system32下的msvcrt.dll。在Unix下在/usr/lib下,一般叫libc.so或是glibc.so等名 字。C语言的标准库基本上是操作系统不可缺少的部分。
首先生成hello.o或是hello.lib文件, 然后编译器调用ld或是link.exe将目标文件与C语言的标准库连接。连接在一起的时候,就可以决定, 每个函数的地址。比如main函数在0x400008出,prinft在0x400030 出。然后开始resovle符号。发现hello.o中有一个函数叫main,main中有一个对printf函数的调用,而C语言的标准库中的符号表中 有一个printf的函数,于是main中对printf的函数调用就转到标准库中printf函数的入口去了,函数调用就被翻译成一条汇编指令比如叫 call 0x400030了。最后会给程序加上一段stratup代码,这段代码完成一些初始化工作比如读参数,共享文件表等等,然后调用查找叫main的函数, 调用main。连接的过程就完成了。
当程序大的时候,需要多个源文件。会产生多个目标文件,可能目标文件a调用了目 标文件b内的函数,这些最后都是在连接的时候resolve的。还是先举例子,比如有两个文件hello.c和foo.c,那么编译的时候可以写成gcc -o hello hello.c foo.c,但是实际上编译器是这样作的,
下面说说连接时容易产生的问题:
用 -c选项可以让编译器不连接,如 gcc -c hello.c,或是cl -c hello.c 这样会只将源文件编译成目标文件。Unix下叫hello.o,Windows下叫hello.obj。目标文件是不能执行的,但是目标文件中已经是可以 执行的机器指令了。其实目标文件和最后生成的可执行文件一样,都是一组函数(函数就是一段一段的机器指令),两者的区别在于目标文件中对函数的调用都是按 名字调用的,而可执行程序中已经是按地址调用了。先举例子,比如hello.c 程序中调用了printf("hello.c");。编译成的目标文件中,会有一个符号表,其中有一个符号叫printf,并且表明他是一个函数。但是由 于这个函数实际在标准库中,所以还不知道这个函数的具体细节,只有符号在那里。Hello world例子中,自己写的只有一个源文件,编译之后只需要与C语言的标准库连接就可以。
C语言的标准库实际就是一 组函数。在Windows下你安装了VC或是VS会给你安装上,在%VCHOME%\lib目录下,叫msvcrt.lib。这个lib文件实际使用的 是%Windows%\system32下的msvcrt.dll。在Unix下在/usr/lib下,一般叫libc.so或是glibc.so等名 字。C语言的标准库基本上是操作系统不可缺少的部分。
首先生成hello.o或是hello.lib文件, 然后编译器调用ld或是link.exe将目标文件与C语言的标准库连接。连接在一起的时候,就可以决定, 每个函数的地址。比如main函数在0x400008出,prinft在0x400030 出。然后开始resovle符号。发现hello.o中有一个函数叫main,main中有一个对printf函数的调用,而C语言的标准库中的符号表中 有一个printf的函数,于是main中对printf的函数调用就转到标准库中printf函数的入口去了,函数调用就被翻译成一条汇编指令比如叫 call 0x400030了。最后会给程序加上一段stratup代码,这段代码完成一些初始化工作比如读参数,共享文件表等等,然后调用查找叫main的函数, 调用main。连接的过程就完成了。
当程序大的时候,需要多个源文件。会产生多个目标文件,可能目标文件a调用了目 标文件b内的函数,这些最后都是在连接的时候resolve的。还是先举例子,比如有两个文件hello.c和foo.c,那么编译的时候可以写成gcc -o hello hello.c foo.c,但是实际上编译器是这样作的,
gcc -c hello.c上面3行命令的意义应该很清楚。
gcc -c foo.c
ld -0 hello hello.o foo.o
下面说说连接时容易产生的问题:
- unresolved symbol:这个问题一般是忘了连接某个库,或是连接某个目标文件造成的。
- 符号已定义,或是符号冲突:这个问题一般是有函数或变量重名造成的。
取属性消耗的时间
以前是用C/C++的,对于数组循环都是for(int i=0; i<n; i++) .......
因为c的数组没有任何附加信息就是一个首地址。现在开始用java和c#,一开始喜欢写 for(int i=0; i<ar.Length; i++)... 这样的代码。现在发现取数组长度的属性是有额外消耗的。如下代码
这段代码在我的机器上执行结果如下
time is 62.5
time is 31.25
也就是说取数组长度属性相当于做一次额外的浮点乘法。积累起来这个开销还是很大的。所以应该在循环开始先把长度取出来存到变量中再循环。
DateTime s, e;
s = DateTime.Now;
for (int i = 0; i < ddd.Count; i++)
{
double x = 2.0*3.0;
}
e = DateTime.Now;
TimeSpan c = e - s;
Console.WriteLine(" time is {0}", c.TotalMilliseconds.ToString());
s = DateTime.Now;
for (int i = 0; i < length; i++)
{
double x = 2.0 * 3.0;
}
e = DateTime.Now;
c = e - s;
Console.WriteLine(" time is {0}", c.TotalMilliseconds.ToString());
这段代码在我的机器上执行结果如下
time is 62.5
time is 31.25
也就是说取数组长度属性相当于做一次额外的浮点乘法。积累起来这个开销还是很大的。所以应该在循环开始先把长度取出来存到变量中再循环。
订阅:
帖子 (Atom)

