>백엔드 개발 >PHP 튜토리얼 >PHP 내부 함수를 사용하는 방법

PHP 내부 함수를 사용하는 방법

伊谢尔伦
伊谢尔伦원래의
2017-06-26 10:24:461482검색

함수 정의를 찾는 방법

먼저 strpos 함수의 정의를 찾아보겠습니다.

첫 번째 단계는 PHP 5.4 루트 디렉토리로 이동하여 페이지 상단의 검색 상자에 strpos를 입력하는 것입니다. 검색 결과는 PHP 소스 코드에서 strpos가 나타나는 위치를 보여주는 큰 목록입니다.

이 결과는 우리에게 그다지 도움이 되지 않으므로 약간의 트릭을 사용합니다. strpos 대신 "PHP_FUNCTION strpos"(큰따옴표를 놓치지 마세요. 중요합니다.)를 검색합니다.

이제 두 개의 항목을 얻습니다. 링크:

/PHP_5_4/ext/standard/

php_string.h 48 PHP_FUNCTION(strpos);

string.c 1789 PHP_FUNCTION(strpos)

가장 먼저 주목해야 할 점은 두 위치 모두 ext /standard 폴더에 있다는 것입니다. strpos 함수(대부분의 문자열, 배열 및 파일 함수와 마찬가지로)가 표준 확장의 일부이기 때문에 이것이 우리가 찾을 것으로 기대하는 것입니다.

이제 새 탭에서 두 링크를 열고 그 뒤에 어떤 코드가 숨겨져 있는지 확인하세요.

첫 번째 링크를 클릭하면 다음 코드가 포함된 php_string.h 파일로 연결되는 것을 볼 수 있습니다.

// ...
PHP_FUNCTION(strpos);
PHP_FUNCTION(stripos);
PHP_FUNCTION(strrpos);
PHP_FUNCTION(strripos);
PHP_FUNCTION(strrchr);
PHP_FUNCTION(substr);
// ...

일반적인 헤더 파일(접미사 .h로 끝나는 파일)은 다음과 같습니다. 간단한 함수 목록, 함수는 다른 곳에서 정의됩니다. 사실 우리는 우리가 찾고 있는 것이 무엇인지 이미 알고 있기 때문에 이 중 어떤 것에도 관심이 없습니다.

두 번째 링크는 더 흥미롭습니다. 함수의 실제 소스 코드가 포함된 string.c 파일로 연결됩니다.

이 기능을 단계별로 안내하기 전에 먼저 이 기능을 직접 이해해 보시기 바랍니다. 아주 간단한 기능이고, 실제 세부사항을 모르더라도 대부분의 코드는 명확해 보입니다.

PHP 함수의 골격

모든 PHP 함수는 동일한 기본 구조를 사용합니다. 각 변수는 함수 상단에 정의된 다음 zend_parse_parameters 함수가 호출되고 RETURN_*** 및 php_error_docref에 대한 호출을 포함하여 기본 논리가 제공됩니다.

그럼 함수 정의부터 시작해 보겠습니다.

zval *needle;

char *haystack;

char *found = NULL;

char needle_char[2];

long offset = 0;

int haystack_len;

첫 번째 줄은 zval을 가리키는 포인터 바늘을 정의합니다. zval은 PHP 내의 모든 PHP 변수를 나타내는 정의입니다. 실제 모습은 다음 기사에서 논의될 것입니다.

두 번째 줄은 단일 문자에 대한 포인터인 haystack을 정의합니다. 이 시점에서 C 언어에서 배열은 첫 번째 요소에 대한 포인터를 나타낸다는 점을 기억해야 합니다. 예를 들어, haystack 변수는 전달한 $haystackstring 변수의 첫 번째 문자를 가리킵니다. haystack + 1은 두 번째 문자를 가리키고, haystack + 2는 세 번째 문자를 가리킵니다. 따라서 포인터를 하나씩 증가시키면 전체 문자열을 읽을 수 있습니다.

그런 다음 문제가 발생합니다. PHP는 문자열이 끝나는 위치를 알아야 합니다. 그렇지 않으면 멈추지 않고 포인터를 계속 증가시킵니다. 이 문제를 해결하기 위해 PHP는 haystack_len 변수인 명시적인 길이도 저장합니다.

이제 위 정의에서 우리는 함수의 세 번째 매개변수인 검색을 시작하기 위한 오프셋을 저장하는 데 사용되는 오프셋 변수에 관심이 있습니다. int와 마찬가지로 정수 데이터 유형인 long을 사용하여 정의됩니다. 이제 둘의 차이는 중요하지 않지만 알아야 할 것은 PHP에서는 정수값은 long으로 저장되고, 문자열의 길이는 int로 저장된다는 점이다.

이제 다음 세 줄을 살펴보세요.

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) {
    return;
}

이 세 줄의 코드에서 수행하는 작업은 함수에 전달된 매개변수 를 가져와 위에 선언된 변수에 저장하는 것입니다.

함수에 전달되는 첫 번째 매개변수는 전달되는 매개변수의 개수입니다. 이 번호는 ZEND_NUM_ARGS() 매크로를 통해 제공됩니다.

다음 함수는 PHP의 기능인 TSRMLS_CC 매크로입니다. 이 이상한 매크로는 PHP 코드 베이스의 여러 곳에 흩어져 있는 것을 발견할 수 있습니다. PHP가 여러 스레드에서 변수를 섞지 않도록 보장하는 TSRM(Thread-Safe Resource Manager)의 일부입니다. 이는 우리에게 그다지 중요하지 않습니다. 코드에 TSRMLS_CC(또는 TSRMLS_DC)가 표시되면 무시하세요. (주의해야 할 한 가지 이상한 점은 "인수" 앞에 쉼표가 없다는 것입니다. 이는 스레드 안전을 사용하여 함수를 생성하는지 여부에 관계없이 매크로가 비어 있거나 trsm_ls로 해석되기 때문입니다. 따라서 쉼표는 일부입니다. )

이제 중요한 사항에 도달했습니다. "sz|l" 문자열은 함수가 수신한 매개변수를 표시합니다. :

s  // 第一个参数是字符串
z  // 第二个参数是一个zval结构体,任意的变量
|  // 标识接下来的参数是可选的
l  // 第三个参数是long类型(整型)

s, z, l 외에도 더 많은 로고 유형이 있지만 대부분 문자를 통해 의미를 명확하게 알 수 있습니다. 예를 들어 b는 부울, d는 double(부동 소수점 수), a는 배열, f는 콜백(함수), o는 객체입니다.

接下来的参数&haystack;,&haystack;_len,&needle;,&offset;指定了需要赋值的参数的变量。你可以看到,它们都是使用引用(&)传递的,意味着它们传递的不是变量本身,而是指向它们的指针。

这个函数调用之后,haystack会包含haystack字符串,haystack_len是字符串的长度,needle是needle的值,offset是开始的偏移量。

而且,这个函数使用FAILURE(当你尝试传递无效参数到函数时会发生,比如传递一个数组赋值到字符串)来检查。这种情况下zend_parse_parameters函数会抛出警告,而此函数马上返回(会返回null给PHP的用户层代码)。

在参数解析完毕以后,主函数体开始:

if (offset < 0 || offset > haystack_len) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
    RETURN_FALSE;
}

这段代码做的事情很明显,如果offset超出了边界,一个E_WARNING级别的错误会通过php_error_docref函数抛出,然后函数使用RETURN_FALSE宏返回false。

php_error_docref是一个错误函数,你可以在扩展目录找到它(比如,ext文件夹)。它的名字根据它在错误页面中返回文档参考(就是那些不会正常工作的函数)定义。还有一个zend_error函数,它主要被Zend Engine使用,但也经常出现在扩展代码中。

两个函数都使用sprintf函数,比如格式化信息,因此错误信息可以包含占位符,那些占位符会被后面的参数填充。下面有一个例子:

php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename);
// %d is filled with Z_STRLEN_PP(tmp)
// %s is filled with filename

让我们继续解析代码:

if (Z_TYPE_P(needle) == IS_STRING) {
    if (!Z_STRLEN_P(needle)) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");
        RETURN_FALSE;
    }
 
    found = php_memnstr(haystack + offset,
                        Z_STRVAL_P(needle),
                        Z_STRLEN_P(needle),
                        haystack + haystack_len);
}

前面的5行非常清晰:这个分支只会在needle为字符串的情况下执行,而且如果它是空的话会抛出错误。然后到了比较有趣的一部分:php_memnstr被调用了,这个函数做了主要的工作。跟往常一样,你可以点击该函数名然后查看它的源码。

php_memnstr返回指向needle在haystack第一次出现的位置的指针(这就是为什么found变量要定义为char *,例如,指向字符的指针)。从这里可以知道,偏移量(offset)可以通过减法被简单地计算,可以在函数的最后看到:

RETURN_LONG(found - haystack);

最后,让我们来看看当needle作为非字符串的时候的分支:

else {
    if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
        RETURN_FALSE;
    }
    needle_char[1] = 0;
 
    found = php_memnstr(haystack + offset,
                        needle_char,
                        1,
                        haystack + haystack_len);
}

我只引用在手册上写的”如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符顺序值。”这基本上说明,除了写strpos($str, 'A'),你还可以写strpos($str, 65),因为A字符的编码是65。

如果你再查看变量定义,你可以看到needle_char被定义为char needle_char[2],即有两个字符的字符串,php_needle_char会将真正的字符(在这里是’A’)到needle_char[0]。然后strpos函数会设置needle_char[1]为0。这背后的原因是因为,在C里面,字符串是使用’’结尾,就是说,最后一个字符被设置为NUL(编码为0的字符)。在PHP的语法环境里,这样的情况不存在,因为PHP存储了所有字符串的长度(因此它不需要0来帮助找到字符串的结尾),但是为了保证与C函数的兼容性,还是在PHP的内部实现了。

Zend functions

我对strpos这个函数感觉好累,让我们找另一个函数吧:strlen。我们使用之前的方法:

从PHP5.4源码根目录开始搜索strlen。

你会看到一堆无关的函数的使用,因此,搜索“PHP_FUNCTION strlen”。当你这么搜索的时候,你会发现一些奇怪的事情发生了:没有任何的结果。

原因是,strlen是少数通过Zend Engine而不是PHP扩展定义的函数。这种情况下,函数不是使用PHP_FUNCTION(strlen)定义,而是ZEND_FUNCTION(strlen)。因此,我们也要搜索“ZEND_FUNCTION strlen”。

我们都知道,我们需要点击没有分号结尾的链接跳到源码的定义。这个链接带我们到下面的函数定义:

ZEND_FUNCTION(strlen)
{
    char *s1;
    int s1_len;
 
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s1, &s1_len) == FAILURE) {
        return;
    }
 
    RETVAL_LONG(s1_len);
}

这个函数实现太简单了,我不觉得我还需要进一步的解释。

方法

我们会谈论类和对象如何工作的更多细节在其他文章里,但作为一个小小的剧透:你可以通过在搜索框搜索ClassName::methodName来搜索对象方法。例如,尝试搜索SplFixedArray::getSize。

위 내용은 PHP 내부 함수를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.