php写时复制

背景:

我们定义两个变量a和b,并且他们的值都是1。站在内存角度考虑,希望他们公用一份内存。未来某个变量被修改时,再为其申请一份内存空间,这个操作名为写时复制。

<?php
    $foo = 1;
    xdebug_debug_zval('foo');
    $bar = $foo;
    xdebug_debug_zval('foo');
    $bar = 2;
    xdebug_debug_zval('foo');
?>
//-----执行结果-----
foo: (refcount=1, is_ref=0)=1
foo: (refcount=2, is_ref=0)=1
foo: (refcount=1, is_ref=0)=1

分解来看:

1.第一个debug输出,定义foo变量,在内存开辟一块空间,foo变量指向那块空间;

2.第二个debug输出,将foo赋给bar变量。注意此处并不为bar申请新空间,而是将bar指向最开始开辟的那块空间。即此时foo和bar同时指向同一块空间;

3.第三个debug输出,由于bar的值为修改,所以为bar申请一块新的内存空间。

注意引用

显示引用会破坏写时复制,举个例子:

<?php
$foo['love'] = 1;
$bar  = &$foo['love'];
$tipi = $foo;
$tipi['love'] = '2';
echo $foo['love'];

最后输出结果为2。因为bar的引用,使foo[‘love’]变成了引用,从而使Zend没有对tipi的修改产生内存的复制分离。

参考资料

写时复制(Copy On Write)

那些年踩过PHP的坑——sleep

一、背景

打算写一个专题,记录PHP踩过的坑,以sleep函数开篇。

二、函数说明

int sleep ( int $seconds )

程序延迟执行指定的 seconds 的秒数。
成功时返回 0,错误时返回 FALSE。

三、坑点

参数传入小数,例如程序中sleep(0.5)不能起到作用,因为0.5转化成整型为0。即sleep(0),所以不能起到延缓执行的作用。

四、解决方案

使用usleep函数

void usleep ( int $micro_seconds )

以指定的微秒数延缓程序的执行。例如延缓0.5秒执行,即usleep(500000)。
附官网手册下的一则评论:

This may seem obvious, but I thought I would save someone from something that just confused me: you cannot use sleep() to sleep for fractions of a second. This:

<?php sleep(0.25) ?>

will not work as expected. The 0.25 is cast to an integer, so this is equivalent to sleep(0). To sleep for a quarter of a second, use:

<?php usleep(250000) ?>

五、参考资料

什么是cookie和session——二者有何区别

一、概述

由于http具有五大特点:

  • 支持客户/服务器模式
  • 简单快速
  • 灵活
  • 无连接
  • 无状态
    其中无连接、无状态与该主题有关。

无连接,即每次连接只处理一个请求,处理完就断开连接。优点是可以尽快的释放出资源给其他客户端;但是随着互联网发展,网页中嵌套的资源越来越多,每次请求资源都建立一次TCP变得低效。于是keep-alive被提出来,使客户端到服务端的连接持续有效。keep-alive的缺点是在处理暂停期间,本可以释放的资源仍然被占用,影响性能。

无状态,即HTTP协议对事物处理没有记忆能力,服务端不知道客户端处于什么状态。缺点是若后续处理需要前面的信息,需要将前面的信息重传,导致每次传输的数据量增大。

经典的例子,购物这点击下单按钮时,需要知道用户购买了那些物品。

根本上来讲,cookie和session是为了解决HTTP协议无状态的特型应运而生的。

二、cookie

网站为了辨别用户身份而存储在用户本地终端上的数据(通常经过加密)。
cookie分为两种:

  • 内存cookie,由浏览器维护,存储在内存里;
  • 硬盘cookie,保存在硬盘里,有一个过期时间。

三、session

服务端保存的用来跟踪用户状态的数据。

四、区别

1.存取方式不同

cookie中只能保存ASCII字符,不能直接存取对象;
session中能够存取任何数据类型。

2.隐私策略不同

cookie存储在客户端,对用户可见,可以被修改、复制;
session存储在服务端,对用户透明,安全性更高。

3.有效期不同

cookie可以设置长期有效,只要为其设置一个特别大的过期时间;
session有效期较短。

4.服务器压力不同

cookie保存在客户端,不占用服务器资源;
session保存在服务端,当用户并发量大时,会对服务器造成压力。

5.浏览器支持不同

cookie需要客户端浏览器支持。若浏览器禁用或不支持cookie,则需要使用session及URL地址重写(把session id直接附在URL路径后面),可以设置为一切窗口内有效或当前窗口及子窗口有效;
session只在本次浏览器窗口及子窗口内有效。

6.跨域支持上不同

cookie支持跨域名访问;
session不支持跨域名访问,session仅在它所在的域名内有效。

五、参考资料

为什么不要点击小广告——CSRF攻防

一、什么是CSRF

全称Cross-site request forgery,译作跨站请求伪造。简单来说,就是利用技术手段欺骗用户通过浏览器去访问自己曾经认证过的网站。

二、攻击场景

Eric通过向银行网站 https://bank.com?count=eric&fro=sam&money=100 发起请求,给Sam转账100块。银行网站通常会验证这是否来自于一个合法的session,同时该session的帐号是否已登录。

Luck虽然可以构造请求 https://bank.com?count=eric&for=luck&money=100 向银行网站发送,但是由于验证不通过,所以不能够获得Eric的转账。

此时Luck开始利用CSRF攻击,做一个网站,在网站中放入代码 src=”https://bank.com//count=eric&for=luck&money=100″,然后通过广告等手段诱使Eric触发代码生效。

于是从Eric的浏览器想银行网站发出了 https://bank.com?count=eric&for=luck&money=100 的请求,若此时Eric刚访问银行网站不久,其session尚未过期。那么此次请求将通过验证,Eric的100块就到Luck账户下了。

三、如何防范

1.验证referer

即检查当前请求的来源,例如上面攻击场景的例子。Eric登录银行网站 https://bank.com 后,referer中会携带 bank.com的信息,如果发现请求中referer不是来自 bank.com,则一律拒绝。

优点:简单便捷。

缺点:某些浏览器允许用户修改referer;部分用户为了保护自己的隐私,决绝浏览器提供referer;另外就是在代码中构造referer也可以进行伪造。

2.请求中添加token验证

用户登录后生成一个token放进session,每次请求时将token一起传递,服务端从session中取token与请求参数中的token校验。

优点:相较于验证referer,安全性高一点。

缺点:如何让请求中都带上token参数很麻烦,有的实现是在页面加载时用JS遍历DOM树,添加token参数;token本身有安全性问题,论坛网站中黑客通过发指向自己网站链接的帖子,进而可以获取token。

3.在HTTP头中自定义属性并验证

使用XMLHttpRequest类进行请求,所有该类发起的请求都带上token参数。由于XMLHttpRequest类的请求不会被浏览器记录,所以不用担心token泄露,也不用考虑如何添加token参数。

优点:不用考虑添加麻烦的token参数,同时不会被浏览器记录。

缺点:XMLHttpRequest类通常用于Ajax异步刷新请求,并非所有类都适用于这个请求;由于不会被浏览器所记录,所以无法做前进、后退、收藏、刷新等操作,给用户带来不便。

四、参考资料

PHP代码贡献规范

代码实现

  1. 在源文件和手册里为你的代码完善相关文档。
  2. 以指针为参数的函数,在函数中不应该被释放。
    例如
function int mail(char *to, char *from)

例外情况:

  • 函数的指定行为就是做释放操作。例如efree()
  • 参数中给定一个bool值的参数,控制函数是否能够释放它的参数(如果为true,函数必须释放参数;如果为false,函数不可以释放参数)
  • 低级解析程序,和token cache(令牌缓存?)紧密结合,最小化内存复制开销的代码
  1. 模块内函数间紧密结合,互相依赖。应该声明为’static’。如果可以的话尽量避免这种情况。
  2. 尽可能使用定义和宏指令,以便于常量具有有意义的名字并且容易理解。唯一的例外是对于0和1,当他们分别作为false和true使用时,应该通过 #define 来定义整型常量,进而指定不同行为或动作。

  3. 当编写字符串处理函数时,尽量不要使用strlen()来获取字符串长度。因为php中字符串的length属性已经保存了它的长度,并且它是高效且二进制安全的。

  4. 永远不要使用strncat()。除非你非常确信你在做什么,除此之外,避免使用它。

  5. 在PHP源文件中使用PHP_*宏指令,在ZEND部分源文件中使用ZEND_*宏指令。尽管PHP_*混叠到ZEND_*宏,但是分开来命名更利于理解。
  6. 当使用#if声明注释代码时,建议使用”_0″的方式,例如#if username_0。发生问题时方便追溯代码注释的原因,尤其是在依赖文件中。
  7. 不要定义一个不可用的函数。例如library中缺失一个函数,不要在php版本中定义这个函数,不要抛出函数不存在的运行错误。端用户需要使用function_exists()来检测函数是否存在。
  8. 推荐使用emalloc()、efree()、estrdup()函数。相对于C标准库而言,这些函数内部实现了”safety-net”机制。确保在请求结束时,任何未释放的内存都会得到释放。在调试模式下,他们还提供了有用的分配和溢出信息。
    几乎所有情况下,分配内存必须使用emalloc()函数。
    极少数的情况下需要使用malloc()分配内存,例如第三方库需要控制或释放内存、the memory in
    question needs to survive between multiple requests(内存存活于多个请求间?)。

函数使用/方法命名约定

  1. user-level function命名需要附加进PHP_FUNCTION()宏。函数命名小写,下划线分隔,短小易读。当单词本身就很短时,不应该使用缩写。
    例如:
str_word_count
get_html_translation_table => html_get_trans_table
  1. 若函数是父集合函数的一部分的话,命名时应将父函数的名字加进来,并且明确声明函数间的关系。格式为parent_*
    例如:
存在一个foo函数,其子函数:
foo_select_bar
foo_insert_baz
  1. 用户函数命名应该加上_php_前缀,后面加上紧跟若干小写单词,下划线分隔。如果可以,他们应该被声明为”static”
  2. 变量名必须有意义。避免只有一个字母的变量名,除非变量没有实际意义,例如for (i=0; i<100; i++)
  3. 变量名使用小写,下划线分隔。
  4. 方法名采用”studlycaps”的格式,尽可能短小易读。首字母小写,之后每个单词首字母大写。
  5. 类使用描述性命名,避免使用缩写。类名中每个单词首字母大写,没有下划线分隔。类名前缀应该与父类名相同。

接口函数命名规范

  1. 为避免函数命名冲突,外部接口函数的命名格式为”php_modulename_function()”。小写、下划线分隔。对外暴露的接口需要在php_modulename.h中定义。
    例如:
// PHPAPI
char *php_session_create_id(PS_CREATE_SID_ARGS);

不对外暴露的接口应该被声明为static,同时不应该定义在php_modulename.h中。
例如:

static int php_session_destroy()
  1. 主模块源文件必须被命名为”modulename.c”
  2. 被其他源文件使用的头文件必须命名为”php_modulename.h”

语法和缩进

  1. 永远不要使用C++风格的注释,推荐使用C风格的注释。PHP使用C写的,旨在任意ANSI-C编译器下编译。尽管许多编译器C代码中出现C++风格的注释,但是不能确定代码能够在其他编译器下编译。唯一例外是Win32-specific,由于Win32端口是MS-Visual C++ specific,这个编译器允许C代码中出现C++风格的注释。
  2. 使用K&R风格传送门,缩进风格:传送门
  3. 不要吝啬使用空格和缩进,在函数的声明和定义间、函数中的逻辑语句间加入一个空行。两个函数间至少有一个空行,两个更好。
    例如:
if (foo) {
    bar;
}

to:

if(foo)bar;
  1. 缩进时使用制表符,一个制表符代表四个空格。
  2. 预处理语句(例如#if)必须从第一列开始。预处理指令缩进时,行首必须以#开头,后面跟若干个空格。

测试

  1. 插件应该使用*.phpt测试,具体参阅README.TESTING。

文件和折叠钩子

为了确保在线文件和代码一致,每个user-level函数应该有一个简短的描述,介绍函数的作用。
例如:

/* {{{ proto int abs(int number)
    Returns the absolute value of the number */
PHP_FUNCTION(abs)
{
   ...
}
/* }}} */

‘{{{‘是Emacs和Vim中折叠模式的默认符号(set fdm=marker)。当处理大文件时折叠非常有用,通过折叠可以快速浏览那些你期望看到的函数。函数结尾’}}}’标志折叠结束,单独成行。

关键字’proto’的作用是帮助doc/genfuncsummary脚本生成一个所有函数的摘要,避免代码中折叠引起摘要混乱的问题。

可选参数写法如下:

/* {{{ proto object imap_header(int stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]])
     Returns a header object with the defined parameters */

即使很长,也请保持函数原型在同一行。

新的/实验性的函数

为了解决问题,通常在函数首次公开实现时会有一些函数集合。建议在函数目录中包含一个标记为”EXPERIMENTAL”的文件,同时函数最初实现时遵循标准的前缀约定。
“EXPERIMENTAL”文件应该包含一些信息:

Any authoring information (known bugs, future directions of the module).
Ongoing status notes which may not be appropriate for Git comments.

即所有的作者信息(已知bug,模块未来的计划)。不间断的状态通知可能不适用于GIT注释。
通常新特性应该放到PECL或者experimental分支,除非有特殊原因才可以直接添加进核心仓库。

别名&遗留文档

你或许不赞成使用近乎重复的命名,比如somedb_select_result和somedb_selectresult。

For
documentation purposes, these will only be documented by the most
current name, with the aliases listed in the documentation for
the parent function.

user-functions名字不同,但是别名相同,例如highlight_file和show_source,将分开记录。proto仍然应该包含进来,描述是哪一个函数是别名。

向后兼容函数和名字应原来保持一致。更多内容请查看PHP文档库的README文件。

参考文献

CODING_STANDARDS

Number of Lines To Write String

题目链接

传送门

题目大意

给定一个由小写字母a~z组成的字符串S,再给出一个widths数组,widths[0]代表字符a的宽度、widths[1]代表字符b的宽度等等。每行最宽100个单元,求将字符串S中每个字符替换成实际宽度后,所得行数和最后一行的宽度。

解题思路

用lineSum变量记录当前每行使用的宽度。
如果lineSum + 当前字符宽度 <= 100,则加入当前行;
否则移到下一行,row++,lineSum=0清空。
时间复杂度O(n)。

参考代码

class Solution {
public:
    vector<int> numberOfLines(vector<int>& widths, string S) {
        int length = S.length();
        int lineSum = 0;
        int row = 1;
        for (int i = 0; i < length; i ++) {

            if (lineSum + widths[S[i] - 'a'] <= 100) {
                lineSum += widths[S[i] - 'a'];   
            } else {
                row ++;
                lineSum = 0;
                lineSum += widths[S[i] - 'a'];   
            }
        }

        vector<int> ret;
        ret.push_back(row);
        ret.push_back(lineSum);
        return ret;
    }
};