感觉自己C使用的已经很可以了, 但今天又发现了个问题。

其实这个问题以前好象有人问起,但自己也没怎么注意, 没办法,今天非得回答出来不可。 没办法查了一下。

CU BBS上有下面这篇文章, 很不错, 转来学习一下。 自己也写了个小测试东东上来看看结果:

自己的测试环境: AS3U3

#include int main(int argc, char **argv)

{

char str1[]   = "china";

char str2[10] = "china";

char *str3    = "china";

char str4[]   = {'c', 'h', 'i', 'n', 'a'};

printf("str1=%d str2=%d str3=%d str4=%d\n"

, sizeof(str1), sizeof(str2), sizeof(str3), sizeof(str4));

return (0);

}

[gan@RHEL3 tmp]$ a.out

str1=6 str2=10 str3=4 str4=5

其实具体有什么特别大的区别还是说不太清楚, 很含糊, 决定把汇编好好复习一下, 一定要把这个问题解决。

=======================

下面来源:

非常不错的一篇文章。原文如下:

个人的浅显认识, 欢迎批评指正.

1. 什么是数组类型?

下面是C99中原话:

An array type describes a contiguously allocated nonempty set of objects with a

particular member object type, called the element type.36) Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T , the array type is sometimes called ‘‘array of T ’’. The construction of an array type from an element type is called ‘‘array type derivation’’.

很显然, 数组类型也是一种数据类型, 其本质功能和其他类型无异:定义该类型的数据所占内存空间的大小以及可以对该类型数据进行的操作(及如何操作).

2. 数组类型定义的数据是什么?它是变量还是常量?

char s[10] = "china";

在这个例子中, 数组类型为 array of 10 chars(姑且这样写), 定义的数据显然是一个数组s.

下面是C99中原话:

An lvalue is an expression with an object type or an incomplete type other than void; if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.

看了上面的定义, 大家应该明白了modifiable lvalue和lvalue的区别, 大家也应该注意到array type定义的是lvalue而不是modifiable lvalue.所以说s是lvalue.

s指代的是整个数组, s的内容显然是指整个数组中的数据, 它是china\0****(这里*表示任意别的字符).s的内容是可以改变的, 从这个意义上来说, s显然是个变量.

3. 数组什么时候会"退化"

下面是C99中原话:

Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.

上面这句话说的很清楚了, 数组在除了3种情况外, 其他时候都要"退化"成指向首元素的指针.

比如对 char s[10] = "china";

这3中例外情况是:

(1) sizeof(s)

(2) &s;

(3) 用来初始化s的"china";

除了上述3种情况外,s都会退化成&s[0], 这就是数组变量的操作方式

4. 数组与指针有什么不同?

4.1 初始化的不同

char s[] = "china";

char *p = "china";

在第一句中,以&s[0]开始的连续6个字节内存分别被赋值为:

'c', 'h', 'i', 'n', 'a', '\0'

第二句中,p被初始化为程序data段的某个地址,该地址是字符串"china"的首地址

4.2 sizeof的不同

sizeof就是要求一种数据(类型)所占内存的字节数. 对于4.1中的s和p

sizeof(s)应为6, 而sizeof(p)应为一个"指针"的大小.

这个结果可以从1中对于数组类型的定义和3中数组什么时候不会"退化"中得出来.

4.3 &操作符

对于&操作符, 数组同样不会退化.

4.1中的s和p分别取地址后,其意义为:

&s的类型为pointer to array of 6 chars.

&p的类型为pointer to pointer to char.

4.4 s退化后为什么不可修改

除3种情况外,数组s在表达式中都会退化为"指向数组首元素的指针", 既&s[0]

举个例子

int a;

(&a)++; //你想对谁++? 这显然是不对的

对(&s[0])++操作犹如(&a)++, 同样是不对的,这就导致退化后的s变成不可修改的了.

4.5 二维数组与二级指针

char s[10];与char *p;

char s2[10][8];与char **p2;

s与p的关系,s2与p2的关系,两者相同吗?

紧扣定义的时候又到了.

除3种情况外,数组在表达式中都会退化为"指向数组首元素的指针".

s退化后称为&s[0], 类型为pointer to char, 与p相同

s2退化后称为&s2[0], 类型为pointer to array of 8 chars, 与p2不同

4.6 数组作为函数参数

毫无疑问, 数组还是会退化.

void func(char s[10]); <===> void func(char *s);

void func(char s[10][8]); <===> void func(char (*s)[8]);

4.7 在一个文件中定义char s[8], 在另外一个文件中声明extern char *s. 这样可以吗?

---------file1.c---------

char s[8];

---------file2.c---------

extern char *s;

答案是不可以. 一般来说,在file2.c中使用*s会引起core dump, 这是为什么呢?

先考虑int的例子.

---------file1.c---------

int a;

---------file2.c---------

extern int a;

file1.c和file2.c经过编译后, 在file2.o的符号表中, a的地址是尚未解析的

file1.o和file2.o在链接后, file2.o中a的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern int a;进行的,即0xbf8eafae会被认为是整形a的地址

比如 a = 2; 其伪代码会对应为 *((int *)0xbf8eafae) = 2;

现在再看原来的例子.

---------file1.c---------

char s[8];

---------file2.c---------

extern char *s;

同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的

file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char *s;进行的,即0xbf8eafae会被认为是指针s的地址

比如 *s = 2; 其伪代码会对应为 *(*((char **)0xbf8eafae)) = 2;

*((char **)0xbf8eafae)会是什么结果呢?

这个操作的意思是:将0xbf8eafae做为一个二级字符指针, 将0xbf8eafae为始址的4个字节(32位机)作为一级字符指针

也就是将file1.o中的s[0], s[1], s[2], s[3]拼接成一个字符指针.

那么*(*((char **)0xbf8eafae)) = 2;的结果就是对file1.o中s[0], s[1], s[2], s[3]拼接成的这个地址对应

的内存赋值为2.

这样怎么会正确呢?

下面看看正确的写法:

---------file1.c---------

char s[8];

---------file2.c---------

extern char s[];

同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的

file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char s[];进行的,即0xbf8eafae会被认为是数组s的地址

比如 *s = 2; 其伪代码会对应为 *(*((char (*)[])0xbf8eafae)) = 2;

*((char (*)[])0xbf8eafae)会是什么结果呢?

这个操作的意思是:将0xbf8eafae做为一个指向字符数组的指针, 然后对该指针进行*操作.

这就用到了数组的一个重要性质:

对于数组 char aaa[10];来说, &aaa[0], &aaa, *(&aaa)在数值上是相同的(其实, *(&aaa)之所以在程序中

会在值上等于&aaa[0], 这也是退化的结果: *(&aaa)就是数组名aaa, aaa退化为&aaa[0]).

所以, *((char (*)[])0xbf8eafae)的结果在值上还是0xbf8eafae, 在类型上退化成"指向数组首元素的指针"

那么*(*((char (*)[])0xbf8eafae)) = 2;

其伪代码就成为*((char *)0xbf8eafae) = 2; 即将数组s的第一个元素设为2

5. 小结论

(a). 数组类型是一种特殊类型, 它定义的是数组变量, 是lvalue但不是modifiable lvalue

(b). 除了3种情况外(sizeof, &, 用做数组初始化的字符串数组), 数组会退化成"指向数组首元素的指针"

(c). 不要将数组名简单的看作不可修改的相应的指针, 它们还是有很多不同的

======================================

C语言hk,C语言再学习相关推荐

  1. 3.4 学编程不拘于语言,学语言不限于平台——《逆袭大学》连载

    返回到[全文目录] 目录 3.4 学编程不拘于语言,学语言不限于平台 编程语言 软件和硬件--计算机要作为一个整体看待 语言的江湖 语言不是回事 多平台上的精彩 3.4 学编程不拘于语言,学语言不限于 ...

  2. 认识c语言程序,C语言基础-认识C语言

    前言 如果C语言的基本功扎实,再学习Objective-C,就相对来说容易些.这些都是大家公认的,所以在学习OC之前,有必要对C语言有个基本的认识和了解.我这里讲的也不是很详细,只是粗略的写一些自己对 ...

  3. C语言再学习--关键字

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/53021879 C语言一共有32个关键字,如下表所示: 关键字 说明 auto ...

  4. C语言再学习 -- 创建excel文件

    参看:C语言操作Excel表格 上一篇文章讲了一下 cJSON,可以生成json文件了.这篇文章讲一下怎么生成excel表xsl格式文件. 注意点: 1.文件类型为 xls 或者 xlsx 2.使用f ...

  5. C语言再学习 -- 再论内存管理

    之前有总结过内存管理,参看:C语言再学习 -- 内存管理 但现在看来,缺少示例.从新再写一篇文章,着重介绍常见内存错误.跨函数使用存储区.开始吧,再论内存管理!! 发生内存错误是件非常麻烦的事情.编译 ...

  6. C语言再学习 -- 详解C++/C 面试题 2

    (经典)C语言测试:想成为嵌入式程序员应知道的0x10个基本问题. 参看:嵌入式程序员面试问题集锦 1.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define ...

  7. C语言再学习 -- 详解C++/C 面试题 1

    参看:<高质量C++ C编程指南>.林锐 对这篇文章记忆犹新,因为之前找工作面试的时候,遇到过一家公司就是用的这套面试题.现在就结合考查的知识点和我总结完 C 语言再学习后的深入理解,来详 ...

  8. C语言再学习 -- 再论数组和指针

    之前有总结指针数组,但是现在看来总结的太简单了.好多重要的知识点都是一带而过的.本想在后面添加后来想想算了,还是再写一篇文章来详细介绍数组和指针这对冤家吧. 之前总结的,参看:C语言再学习 -- 数组 ...

  9. C语言再学习 -- 时间函数

    在软件设计中经常会用到关于时间的处理,用来计算语句.函数的执行时间,这时就需要精确到毫秒甚至是微妙的时间.我们首先来介绍一下,时间单位: 时间单位还有:秒(s).毫秒(ms).微秒 (μs).纳秒(n ...

最新文章

  1. Google Pixel 超分辨率--Super Resolution Zoom
  2. AIX的用户和组管理
  3. 微软2011 Build大会:Windows 8盛大出场(转)
  4. linux文件的操作原理简介 以及 实现linux cp命令的代码
  5. Spring-AOP @AspectJ进阶之绑定代理对象
  6. 文末有彩蛋 | 第四周课程回顾与总结
  7. 跟我学 Java 8 新特性之 Stream 流(四)并行流
  8. linux系统的安装程序,Linux系统安装
  9. 如何发布Jar包到Maven中央仓库
  10. windows进程管理小工具procexp.exe查找恶意插件
  11. Spring Boot过滤器和拦截器详解
  12. 计算机桌面保护程序,屏幕保护程序软件
  13. 内存映射文件(专门读写大文件)
  14. python代码加密
  15. HBase简介(很好的梳理资料) - johnny_HITWH
  16. spring boot控制AD域 报错解决
  17. 【数学】均匀分布生成其他分布的方法
  18. Tomcat 启动报错: Failed to execute goal org.apache.tomcat.maven:tomcat7 Could not start Tomcat
  19. 爱是瞬间的美,情是永恒的痛
  20. 计算机基础(10)——截图(1)——鼠标右键菜单怎么截图

热门文章

  1. 【硅谷牛仔】优步CEO,最倒霉的成功创业者 -- 特拉维斯·卡兰尼克
  2. 领域驱动设计门槛很高,没有深厚的面向对象编码能力很难实践成功
  3. 物理看板还是电子看板?
  4. 2018-8-22-粒子滤波
  5. linux shell中小数的运算
  6. c语言输入后没答案,C语言章节习题及答案(无指针)解读.doc
  7. https 请求白屏_记一次HTTPS性能优化
  8. Linux中常见shell命令总结
  9. ICRA 2021自动驾驶相关论文汇总 | 科研党看过来,全文干货
  10. ​多视图立体视觉: CVPR 2019 与 AAAI 2020 上的ACMH、ACMM及ACMP算法介绍