由于C语言中字符串是连续的字符以NUL(注意不是NULL,也没在标准C/C++库中有定义;对于char字符串是'\0',对于wchar_t字符串是L'\0',两者在数值上都是零,可以和int类型的字面量0互相隐式转换)结尾,字符串长度就是连续的非NUL字符的个数。在运行期可以用库函数strlen取char字符串的长度,wcslen取wchar_t字符串的长度。这两个库函数的工作原理(可以说是所有实现)是根据给定的指针参数顺序地查找NUL,如果找到了NUL的话计算和给定参数之间距离的元素数。由于数组等对象类型信息仅在编译期保存,运行期无效,所以数组怎么定义本身是和字符串长度无关的。但是可能有因为出现越界而意外查找到NUL的情况。如LS所说,
char a0 = 0;
char a[] = {'a', 'b', 'c'};
char b0 = 0;
这样在运行期执行函数表达式strlen(a)时,内部的查找指针指向的对象会超出数组a的边界(这里a的长度等于3,关于数组长度以下另外讨论),直到遇到数组外部的NUL。如果是函数内的非静态局部变量,由于对于一般实现,栈的地址生长方向是向下的,因此找到的值等于NUL的char对象就是a0。对于静态存储类(全局或静态变量),一般是顺序地遇到值等于NUL的b0。如果没有这种特殊的保护,strlen有可能一直不能找到NUL直到越界到非法访问内存导致程序崩溃之类的问题为止。
数组长度就是数组定义时元素的个数。注意定义和声明的区别。在没有extern时声明的同时数组被初始化,那么数组声明就同时是定义。数组的初始化有两种形式,一种是隐式初始化,仅适用于全局或静态数组,即没有初始化列表,所有元素被value-initialized(C++的说法),对于整型元素(注意这里包括了char),就是数组的每个元素被zero-initialized(初始化为0)(对于局部非静态数组,这样的语法形式并不初始化数组,元素的值为随机值;如果后面使用了未初始化的数组元素,可能会出现莫名其妙的错误;编译程序可能会填充未初始化的元素,例如Microsoft C&C++ Debugger填充每个未被初始化的栈字节为0xCC,所以用VC++调试可能会发现“烫烫烫烫……”的情况,0xCCCC是“烫”的GBK编码)。另一种是显式初始化,也就是后面跟随={...}这样的列表,其中...表示多个初始化的元素。如果数组定义时不指定长度,那么长度等于初始化列表中元素的个数。如果数组定义时指明了长度,就以[]中的为准。这里要避免初始化列表元素个数不大于[]里的长度,否则C出现未定义行为,C++中是非法的。如果元素个数小于长度,剩余元素被隐式地value-initialized。
(另外,类似char a[];这样声明了一个数组,它的长度是未知的,类型是不完整类型,只能出现在结构体末尾等特殊上下文中(C99支持),可以不讨论。)
char a[]={'a','b','c'};由于后面有初始化列表进行了显式初始化,那么a的长度就由初始化列表中的元素个数给出,等于3。
char a[5]={'a','b','c'};的长度是[]之间的5。由于初始化列表元素数只有3,剩余两个元素被初始化为0。因此实际上等价于char a[5]={'a','b','c',0,0};。而0可以隐式转化为'\0',因此a作为字符串的长度就是'a'到'c'之间的字符数,也就是3.
sizeof在编译期计算一个表达式表示的对象或者一个类型占用存储期空间(以字节计量)。对于数组,sizeof给出整个数组占用的字节数,除以元素字节数就是数组长度了。这里由于sizeof(char)等于1,数组的长度就等于sizeof的结果。
====
[原创回答团]
参考资料:原创 + 引用