Head First C 笔记 - 使用多个源文件

使用多个源文件

大型软件通常会拆分源代码为多个小模块,最终将他们编译成一个可执行程序,关注以下几个关键点。

头文件

函数的定义顺序可能会导致编译错误(使用前需要先定义),头文件的作用就是为了做函数声明,让编译器不用做假设,可以防止编译出错。

共享代码

共同的特性,最好能共享代码。所以多个文件要想共享一份代码的话,自然要将共享的代码放在一个单独的.c文件中。那多个文件如何编译呢?

参考如下案例:
encrypt.h

void encrypt(char *message);

encrypt.c

#include "encrypt.h"
// 用于加密字符串
void encrypt(char *message)
{
    while (*message) {
        *message = *message ^ 31;
        message++;
    }
}

message_hider.c

#include <stdio.h>
#include "encrypt.h"

int main()
{
    char msg[80];
    while (fgets(msg, 80, stdin)) {
        encrypt(msg); // 这样通用的代码就能共享了
        printf("%s\n", msg);
    }
}

如何编译呢?很简单,只需要把源文件都传递给gcc即可。
gcc message_hider.c encrypt.c -o message_hider

使用make

首先需要了解下gcc的编译过程。

  1. 预处理:修改代码
  2. 编译:转换成汇编
  3. 汇编:生成目标代码
  4. 链接:放在一起

一个小的改动,不要重新编译全部的文件,耗时耗力。所以我们可以先将c源文件编译成目标代码文件,如果修改了某个源文件,只需要重新编译这个文件,最后链接目标文件即可。

同样是上面的案例,先编译成目标文件:

gcc -c *.c,生成.o文件

然后链接,编译器可以识别这些文件是目标文件:

gcc *.o -o message_hider

ok,这时如果修改了message_hider.c,那么只需要执行gcc -c message_hider.c,然后重新链接就行了。

新的问题,假设有很多源文件,你记不住修改了哪些文件,该怎么办?可以使用make来自动化这个过程。

encrypt.o: encrypt.c encrypt.h
    gcc -c encrypt.c

message_hider.o: message_hider.c encrypt.h
    gcc -c message_hider.c

message_hider: encrypt.o message_hider.o
    gcc encrypt.o message_hider.o -o message_hider

执行make message_hider,它会自动检查源文件的和目标文件的时间戳,如果有变动,就会重新编译。

Head First C 笔记 - 存储器和指针

存储器和指针

内存的存储结构

  • 栈(Stack):存储函数创建的变量值(局部变量)
  • 堆(Heap):动态存储分配
  • 全局量区(Globals):存储函数定义的变量值(全局变量)
  • 常量区:存储只读数据
  • 代码段:存储代码段

数组变量与指针

相同的地方

且看下面的案例:

#include <stdio.h>

void fortune_cookie(char msg[])
{
    printf("msg is: %s\n", msg); // msg is: abcde
    printf("msg has %lu bytes", sizeof(msg)); // msg has 8 bytes
}

int main(int argc, char const *argv[])
{
    char quote[] = "abcde";
    fortune_cookie(quote);
    return 0;
}

C语言中的字符串其实就是字符数组,上面将quote字符串传递给了函数,输出比较匪夷所思。

为什么sizeof(msg)长度是8?不应该是字符串的长度么?是因为数组变量可以当成指针使用,它指向数组在内存中的起始位置。可以做如下验证,得到的确实是一个地址。

printf("quote addr is %p\n", quote); // quote addr is 0x7ffeef53180a

其实在编译的时候,gcc也有相关的提示信息,根据提示,参数可由char *替代:

main.c:6:39: warning: sizeof on array function parameter will return size of
      'char *' instead of 'char []' [-Wsizeof-array-argument]
    printf("msg has %lu bytes", sizeof(msg));
                                      ^
main.c:3:26: note: declared here
void fortune_cookie(char msg[])
                         ^
1 warning generated.

所以sizeof(msg)不过是指针的大小罢了。

不同的地方

目前看起来数组变量与指针是一回事儿,但其实又不完全相同,还是来看案例:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char s[] = "abcde";
    char *t = s;

    printf("s has %lu bytes\n", sizeof(s)); // s has 6 bytes
    printf("t has %lu bytes\n", sizeof(t)); // t has 8 bytes

    printf("%d\n", &s == s); // 1
    printf("%d\n", &t == t); // 0
}

从上面的案例可以看出:

  1. sizeof(数组)就是数组的长度,这时C语言就开窍了
  2. 数组的地址,,就是数组的地址,&s == s
  3. 数组变量不能指向其他地方 p59

所谓的指针退化,就是上面案例的情况,将数组变量赋值给指针变量(char *t = s),这时候指针变量只会包含数组的地址信息,而对长度却无法感知。我们把这种信息的丢失称为退化

再来看一段代码:

char *cards = "JQK";
cards[1] = "A"; // 报错
char cards1[] = "JQK";
cards1[1] = 'A';

要注意的是,char *cards = "JQK" 是无法修改的,因为"JQK"存贮在了常量区,这部分存储是只读的,cards变量在栈上,它保存了"JQK"的地址。所以最好显示写成 const char *cards = "JQK,这样在编译期就能发现错误了。p73

char cards1[] = "JQK" 数值也存储在常量区,同时会copy到中,相当于创建了副本。所以可以修改。注意这里的cards1变量没有保持在存储器中,因为程序编译期间,会把所有对数组的引用替换成数组的地址。也就是说,在最后的可执行程序中,数组变量并不存在

指针运算

指针变量也是变量,保存的是数字,可以通过&获取它地址。所以指针是可以运算的:

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char s[] = "abcde";
    char *t = s;

    printf("%c\n", t[0]); // a
    printf("%c\n", *t); // a
    printf("%c\n", *(t+2)); // c
}

要注意指针是有类型的,不同类型的指针运算表现会不一样,比如上面案例中,对char指针加1,指针就会指向下一个地址,因为char就占一个字节。但如果是int指针,地址就会加4,因为int通常占4个字节。

int nums[] = {1,2,3};
printf("%p\n", nums); // 0x7ffee69f580c
printf("%p\n", nums+1); // 0x7ffee69f5810,加4

用指针输入数据

看例子即可,主要说明了scanf()fgets()的区别。

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char foo[5];
    scanf("%s", foo);
    printf("%s\n", foo); // 可能会出现 segmentation fault

    char bar[5];
    fgets(bar, sizeof(bar), stdin);
    printf("%s\n", bar);
}

如果忘记了限制scanf()读取字符串的长度,用户输入的长度就有可能超过foo的长度,就有可能导致缓冲区溢出。而fgets()则不会,因为它强行限制了最大长度。

Head First C 读书笔记

最近在重新学习 C 语言,本文是 Head First C 这本书里关于 C 的一些基础知识的总结笔记。

内存存储结构

  • 栈(stack):存储函数创建的变量值(局部变量)
  • 堆(heap):动态存储分配
  • 全局量区:存储函数定义的变量值(全局变量)
  • 常量区:存储只读数据
  • 代码段:存储代码段

指针

数组与指针

  • 数组变量可以被用作指针
  • 数组变量指向数组中的第一个元素
  • 如果把函数参数声明为数组,它会被当称指针处理
  • 指针变量也是变量。。保存的数字,也可以通过 & 获取它的地址

- 阅读剩余部分 -

LuaRocks Tutorial

LuaRocks 是一个 Lua 的包管理工具,提供了一个命令行的方式来管理 Lua 包依赖。官网

安装 Lua 5.1.5

wget http://www.lua.org/ftp/lua-5.1.5.tar.gz
mkdir /usr/local/lua-5.1.5 && cd lua-5.1.5
# 配置 Makefile,指定安装目录,INSTALL_TOP = /usr/local/lua-5.1.5
# aix ansi bsd freebsd generic linux macosx mingw posix solaris 支持的平台
make macosx
make macosx install
ln -sf /usr/local/lua-5.1.5/bin/lua /usr/local/bin/lua

安装 LuaRocks

wget https://luarocks.org/releases/luarocks-2.4.1.tar.gz
tar zxpf luarocks-2.4.1.tar.gz && cd luarocks-2.4.1
mkdir /usr/local/luarocks-2.4.1
./configure --prefix=/usr/local/luarocks-2.4.1 --with-lua=/usr/local/lua-5.1.5
make build  && make install

使用 LuaRocks 安装包

luarocks install dkjson
require("dkjson") 然后就可以使用了

贡献 Rock 包

创建 .rockspec 文件,下面是我写的一个名为 nginx-lua-frequency 的 Lua 模块的案例。

package = "nginx-lua-frequency"
version = "0.1-1"
source = {
   url = "git://github.com/itsmikej/nginx-lua-frequency.git"
}
description = {
   summary = "A frequency module for Nginx written in Lua",
   homepage = "https://github.com/itsmikej/nginx-lua-frequency",
   maintainer = "Jiang Yang<jiangyang33@gmail.com>",
   license = "MIT"
}
dependencies = {
   "lua=5.1",
   "lua-resty-memcached=0.13-0"
}
build = {
   type = "builtin",
   modules = {
      ["frequency"] = "src/frequency.lua",
      ["frequency.adapter.memcached"] = "src/adapter/memcached.lua"
   }
}

保存文件名为 nginx-lua-frequency-0.1-1.rockspec,格式必须是"{package}-{version}.rockspec",最后上传即可:
luarocks upload nginx-lua-frequency-0.1-1.rockspec --api-key ********
执行完这个命令后,会在当前目录生成一个名为 nginx-lua-frequency-0.1-1.src.rock 的二进制文件,其实这个文件就是当前项目的一个压缩包,使用 luarocks install 安装时,其实就是用的这个压缩包。

BTW

nginx-lua-frequency 是一个基于 memcached 的通用频率限制模块,可同时做多个时间维度(秒,分,小时)的请求频率配置。Github链接

参考

https://github.com/luarocks/luarocks/wiki/Documentation
http://www.jianshu.com/p/196b5dad4e78
https://segmentfault.com/a/1190000003920034

Lua 中点号和冒号操作符的备忘

分两种情况来看

函数定义

冒号默认会隐式 接收 一个 self 参数,使用点号则需要显示传入 self

函数调用

冒号默认会隐式 传入 一个 self 参数,点号只是普通调用

案例

local _M = { _VERSION = "0.0.1" }

function _M.new(self, o)
    o = o or {}
    setmetatable(o, {__index = self})
    return o
end

function _M:foo(arg1, arg2)
    ngx.say(self._VERSION)
    ngx.say(arg1)
    ngx.say(arg2)
    ngx.say("--------")
end

function _M:bar()
    self.foo("arg1", "arg2")
    self.foo(self, "arg1", "arg2")
    self:foo("arg1", "arg2")
end

local ins = _M:new() -- 默认带入了 self 参数

ins.foo("arg1", "arg2")
-- 因为 foo 函数会默认接收 self, arg1 传给了 self,所以第一个参数会输出 nil
-- nil
-- arg2
-- nil

ins.foo(ins, "arg1", "arg2") -- 这样就 OK 了
-- 0.0.1
-- arg1
-- arg2

ins:foo("arg1", "arg2") -- 输出符合预期
-- 0.0.1
-- arg1
-- arg2

ins:bar() -- 同样的输出