【问题标题】:PHP: Why Does array Syntax on a Zero Length String Cast the String as an Array?PHP:为什么零长度字符串上的数组语法将字符串转换为数组?
【发布时间】:2016-05-26 20:48:57
【问题描述】:

在 PHP 中,您可以使用数组语法来访问字符串索引。下面的程序

<?php
$foo = "Hello";
echo $foo[0],"\n";
?>

回声

H

但是,如果您访问零长度字符串的第一个字符

<?php
$bar = "";
$bar[0] = "test";
var_dump($bar);
?>

PHP 将你的字符串变成一个数组。上面的代码产生

array(1) {
    [0] =>
    string(4) "test"
}

即我的零长度字符串被转换为一个数组。类似的“访问字符串的未定义索引”示例不会产生这种强制转换行为。

$bar = " ";
$bar[1] = "test";
var_dump($bar);

产生字符串t。即$bar 仍然是字符串,不会转换为数组。

当语言需要为您推断和/或自动转换变量时,这些不直观的边缘情况是不可避免的,但有人知道幕后发生了什么吗?

即PHP 中的 C/C++ 级别发生了什么来实现这一点。为什么我的变量变成了数组。

PHP 5.6,如果这很重要的话。

【问题讨论】:

  • 不,你通过$bar[0] = "test";创建了一个数组
  • @RiggsFolly 不,我没有,当我说$bar = "" 时,我创建了一个字符串。当我尝试访问字符串 (0) 中不存在的索引时,PHP 将该字符串转换为数组。试试脚本脚本$bar = " "(单字符串)
  • @E_p 我预计会引发错误,或者 PHP 推断我想将字符串从零长度扩展到一,并将一个字符分配给数组的第一个索引,类似于 @ 987654332@ 工作。回复:最后一个,我希望我的编程语言能够始终如一地处理这种奇怪的演员阵容。它没有,我知道会发生这种情况,但我很好奇为什么。
  • @RomualdVilletet 或者在世界上最受欢迎的编程问答网站上提问 :)
  • 没有强制转换,显然在第一个示例中,您正在“访问”已定义字符串的位置。在第二个示例中,您正在“创建”一个索引为 0 的数组元素。$s='hello'; $s=array(0=&gt;'test'); 是否应该触发错误?同样的事情。

标签: php types casting php-internals


【解决方案1】:

在 C 级别上,当使用 [] 运算符完成赋值时,变量将转换为数组。当然,当它是一个字符串时,长度为 0 并且不是未设置的调用类型(例如 unset($test[0]))。

case IS_STRING: {
                zval tmp;

                if (type != BP_VAR_UNSET && Z_STRLEN_P(container)==0) {
                    goto convert_to_array;
                }

https://github.com/php/php-src/blob/PHP-5.6.0/Zend/zend_execute.c#L1156

布尔 false 值发生相同的转换。

case IS_BOOL:
            if (type != BP_VAR_UNSET && Z_LVAL_P(container)==0) {
                goto convert_to_array;
            }

通过测试确认:

<?php
$bar = false;
$bar[0] = "test";
var_dump($bar);

输出:

array(1) { [0]=> string(4) "test" }

当使用真时:

<?php
$bar = true;
$bar[0] = "test";
var_dump($bar);

输出:

WARNING Cannot use a scalar value as an array on line number 3
bool(true)

https://github.com/php/php-src/blob/PHP-5.6.0/Zend/zend_execute.c#L1249

当值为 bool 类型且值为 true 时,将执行以下代码:

case IS_BOOL:
            if (type != BP_VAR_UNSET && Z_LVAL_P(container)==0) {
                goto convert_to_array;
            }
            /* break missing intentionally */

        default:
            if (type == BP_VAR_UNSET) {
                zend_error(E_WARNING, "Cannot unset offset in a non-array variable");
                result->var.ptr_ptr = &EG(uninitialized_zval_ptr);
                PZVAL_LOCK(EG(uninitialized_zval_ptr));
            } else { // Gets here when boolean value equals true.
                zend_error(E_WARNING, "Cannot use a scalar value as an array");
                result->var.ptr_ptr = &EG(error_zval_ptr);
                PZVAL_LOCK(EG(error_zval_ptr));
            }
            break;

PHP 5.6 版使用 ZEND 2.6.0 版

【讨论】:

    【解决方案2】:

    我怀疑 "" 被视为未设置,然后被转换为数组。通常是 "" != null != unset,然而 php 在这方面有点笨拙。

    php > $a="test"; $a[0] = "yourmom"; var_dump( $a );
    string(4) "yest"
    
    php > $a=""; $a[0] = "yourmom"; var_dump( $a );
    array(1) {
      [0]=>
      string(7) "yourmom"
    }
    
    php > var_dump((bool) "" == null);
    bool(true)
    
    php > var_dump((bool) $f == null);
    PHP Notice:  Undefined variable: f in php shell code on line 1
    PHP Stack trace:
    PHP   1. {main}() php shell code:0
    
    Notice: Undefined variable: f in php shell code on line 1
    
    Call Stack:
      470.6157     225848   1. {main}() php shell code:0
    
    bool(true)
    

    【讨论】:

    • 查看我刚刚更新的最后一个示例。同样检查类型并将其用作不同的类型无疑会产生不同的行为。如果您检查 $f 的类型,即使它从未设置过,它也会为 null。
    • 不确定最后一个示例显示了什么 - 加上您使用的是 == 而不是 ===,这意味着您正在将 == 类型的强制行为添加到组合中。
    • 你的整个问题都是基于类型转换的;)
    • 我意识到 :) 但这是我所知道的唯一一种 PHP 为您转换变量而不要求显式转换的情况。当您尝试 == 一个 int 和一个字符串时,PHP 会在后台进行一些转换,但变量会保留在原处。
    • 你从哪里得到null != unset?未定义的变量在使用时会引发错误,但默认值为null。最重要的是,isset 实际上会返回 false,如果您有一个值为 null 的已定义变量,这意味着从技术上讲该变量“未设置”。此外,var_dump($test === null); 会引发错误,但输出 true。
    【解决方案3】:

    我试图在 PHP 源代码中找到会发生这种情况的地方。我对 PHP 内部和一般 C 的经验有限,所以如果我错了,请有人纠正我。

    我认为这发生在zend_fetch_dimension_address

       if (EXPECTED(Z_TYPE_P(container) == IS_STRING)) {
            if (type != BP_VAR_UNSET && UNEXPECTED(Z_STRLEN_P(container) == 0)) {
                zval_ptr_dtor_nogc(container);
    convert_to_array:
                ZVAL_NEW_ARR(container);
                zend_hash_init(Z_ARRVAL_P(container), 8, NULL, ZVAL_PTR_DTOR, 0);
                goto fetch_from_array;
            }
    

    看起来如果容器是一个零长度的字符串,它会在对其进行任何操作之前将其转换为数组。

    【讨论】:

    • 哎呀同样的答案
    【解决方案4】:

    当您对空字符串使用数组语法时,它被更改为数组的原因是因为索引 0 未定义,并且此时没有类型。这是一种案例研究。

    <?php
    $foo = "Hello"; // $foo[0] is a string "H"
    echo $foo[0],"\n"; // H
    $foo[0] = "same?"; // $foo[0] is still a string, "s" note that only the s is kept.
    echo $foo,"\n"; // sello
    echo $foo[0],"\n"; // s
    $foo[1] = "b"; // $foo[1] is a string "b"
    echo $foo,"\n"; // sbllo
    $bar = ""; // nothing defined at position 0
    $bar[0] = "t"; // array syntax creates an array with a string as the first index
    var_dump($bar); // array(1) { [0] => string(1) "t" }
    

    【讨论】:

    • 感谢您的回答,但是 并且当时没有类型 是不正确的。如果您指的是字符串,它确实有一个类型:$bar = "";echo gettype($bar),"\n"; 如果您指的是数组,那么索引未定义是正确的,但是 $bar = "a";$bar[2] = "b";var_dump($bar); 确实 not将 $bar 转换为数组,2 未定义。
    • 如果字符串,我指的是索引 0 处的项目,就像我说的:“因为索引 0 未定义,并且在该点没有类型”。在您刚刚给出的示例中,$bar = "a" 将 bar 设置为字符串,该字符串在内部存储为字节数组 (ref)。正因为如此,我会假设,直到一个变量有一个实际的字符串值(即分配给索引的字节),使用数组语法将允许您存储不限于字节的项目,成为一个普通的数组。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-12
    • 2017-11-27
    • 1970-01-01
    • 2013-03-27
    • 2010-10-15
    • 1970-01-01
    相关资源
    最近更新 更多