下面以eeprom用户程序调用ioctl函数的写操作为例追踪IIC子系统的调用过程。eeprom的用户测试是大部分开发板都自带的。看写一个字节数据的eeprom_write_byte函数的定义:
int eeprom_write_byte(struct eeprom *e, __u16 mem_addr, __u8 data)
{
if(e->type == EEPROM_TYPE_8BIT_ADDR) {
__u8 buf[] = { mem_addr & 0x00ff, data };
return i2c_write_2b(e, buf);
} else if(e->type == EEPROM_TYPE_16BIT_ADDR) {
__u8 buf[] =
{ (mem_addr >> ) & 0x00ff, mem_addr & 0x00ff, data };
return i2c_write_3b(e, buf);
}
fprintf(stderr, "ERR: unknown eeprom type\n");
return -;
}
这里使用的是8位地址,因此调用的是i2c_write_2b函数,为什么是2b?这是eeprom规定的,写数据之前要先写地址。注意buf[0]=要写的地址,buf[1]=要写的字节数据。下面是i2c_write_2b函数的定义:
static int i2c_write_2b(struct eeprom *e, __u8 buf[])
{
int r;
// we must simulate a plain I2C byte write with SMBus functions
r = i2c_smbus_write_byte_data(e->fd, buf[], buf[]);
if(r < )
fprintf(stderr, "Error i2c_write_2b: %s\n", strerror(errno));
usleep();
return r;
}
就调用了i2c_smbus_write_byte_data函数,注意参数的含义,下面是它的定义:
static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command,
__u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
I2C_SMBUS_BYTE_DATA, &data);
}
调用了i2c_smbus_access函数,继续追踪,看i2c_smbus_access函数的定义:
static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command,
int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args; args.read_write = read_write;
args.command = command;
args.size = size;
args.data = data;
return ioctl(file,I2C_SMBUS,&args);
}
首先弄清楚参数的含义,file是使用open打开的文件。read_write表示是读操作还是写操作,这里是写,所以它的值为I2C_SMBUS_WRITE。command是要写的地址。size的值为I2C_SMBUS_BYTE_DATA。最后一个参数data.byte=要写的字节数据。
下面开始进入ioctl系统调用,最后会到达i2c-dev.c中的i2cdev_ioctl函数,看它的定义:
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = file->private_data;
unsigned long funcs; dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg); switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/* NOTE: devices set up to work with "new style" drivers
00000408 * can't use I2C_SLAVE, even when the device node is not
00000409 * bound to a driver. Only I2C_SLAVE_FORCE will work.
00000410 *
00000411 * Setting the PEC flag here won't affect kernel drivers,
00000412 * which will be using the i2c_client node registered with
00000413 * the driver model core. Likewise, when that client has
00000414 * the PEC flag already set, the i2c-dev driver won't see
00000415 * (or use) this setting.
00000416 */
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == ) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
client->addr = arg;
return ;
case I2C_TENBIT:
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return ;
case I2C_PEC:
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return ;
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg); case I2C_RDWR:
return i2cdev_ioctl_rdrw(client, arg); case I2C_SMBUS:
return i2cdev_ioctl_smbus(client, arg); case I2C_RETRIES:
client->adapter->retries = arg;
break;
case I2C_TIMEOUT:
/* For historical reasons, user-space sets the timeout
00000452 * value in units of 10 ms.
00000453 */
client->adapter->timeout = msecs_to_jiffies(arg * );
break;
default:
/* NOTE: returning a fault code here could cause trouble
00000458 * in buggy userspace code. Some old kernel bugs returned
00000459 * zero in this case, and userspace code might accidentally
00000460 * have depended on that bug.
00000461 */
return -ENOTTY;
}
return ;
}
比较简单,根据不同的cmd执行不同的分支。由于ioctl传下来的cmd是I2C_SMBUS,因此直接看444、445行,调用i2cdev_ioctl_smbus函数:
static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
unsigned long arg)
{
struct i2c_smbus_ioctl_data data_arg;
union i2c_smbus_data temp;
int datasize, res; if (copy_from_user(&data_arg,
(struct i2c_smbus_ioctl_data __user *) arg,
sizeof(struct i2c_smbus_ioctl_data)))
return -EFAULT;
if ((data_arg.size != I2C_SMBUS_BYTE) &&
(data_arg.size != I2C_SMBUS_QUICK) &&
(data_arg.size != I2C_SMBUS_BYTE_DATA) &&
(data_arg.size != I2C_SMBUS_WORD_DATA) &&
(data_arg.size != I2C_SMBUS_PROC_CALL) &&
(data_arg.size != I2C_SMBUS_BLOCK_DATA) &&
(data_arg.size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
(data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) &&
(data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) {
dev_dbg(&client->adapter->dev,
"size out of range (%x) in ioctl I2C_SMBUS.\n",
data_arg.size);
return -EINVAL;
}
/* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
00000337 so the check is valid if size==I2C_SMBUS_QUICK too. */
if ((data_arg.read_write != I2C_SMBUS_READ) &&
(data_arg.read_write != I2C_SMBUS_WRITE)) {
dev_dbg(&client->adapter->dev,
"read_write out of range (%x) in ioctl I2C_SMBUS.\n",
data_arg.read_write);
return -EINVAL;
} /* Note that command values are always valid! */ if ((data_arg.size == I2C_SMBUS_QUICK) ||
((data_arg.size == I2C_SMBUS_BYTE) &&
(data_arg.read_write == I2C_SMBUS_WRITE)))
/* These are special: we do not use data */
return i2c_smbus_xfer(client->adapter, client->addr,
client->flags, data_arg.read_write,
data_arg.command, data_arg.size, NULL); if (data_arg.data == NULL) {
dev_dbg(&client->adapter->dev,
"data is NULL pointer in ioctl I2C_SMBUS.\n");
return -EINVAL;
} if ((data_arg.size == I2C_SMBUS_BYTE_DATA) ||
(data_arg.size == I2C_SMBUS_BYTE))
datasize = sizeof(data_arg.data->byte);
else if ((data_arg.size == I2C_SMBUS_WORD_DATA) ||
(data_arg.size == I2C_SMBUS_PROC_CALL))
datasize = sizeof(data_arg.data->word);
else /* size == smbus block, i2c block, or block proc. call */
datasize = sizeof(data_arg.data->block); if ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
(data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
(data_arg.size == I2C_SMBUS_I2C_BLOCK_DATA) ||
(data_arg.read_write == I2C_SMBUS_WRITE)) {
if (copy_from_user(&temp, data_arg.data, datasize))
return -EFAULT;
}
if (data_arg.size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
/* Convert old I2C block commands to the new
00000380 convention. This preserves binary compatibility. */
data_arg.size = I2C_SMBUS_I2C_BLOCK_DATA;
if (data_arg.read_write == I2C_SMBUS_READ)
temp.block[] = I2C_SMBUS_BLOCK_MAX;
}
res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
data_arg.read_write, data_arg.command, data_arg.size, &temp);
if (!res && ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
(data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
(data_arg.read_write == I2C_SMBUS_READ))) {
if (copy_to_user(data_arg.data, &temp, datasize))
return -EFAULT;
}
return res;
}
一大堆的if判断。318行,调用copy_from_user函数将用户空间的数据拷贝到内核空间。这样data_arg的内容就与ioctl第三个参数的内容是一样的了。
322至335行,都是判断,一路走来,我们知道data_arg.size的值为I2C_SMBUS_BYTE_DATA,所以这里的if条件不会成立。
338至344行,如果既不是读操作又不是写操作,那肯定不行,返回出错。
348行,由于不满足第一个条件,所以不会执行if里的语句。
356至360行,我们的data_arg.data是不为NULL的,可以继续往下执行。
362行,data_arg.size == I2C_SMBUS_BYTE_DATA这个条件满足,所以执行364行的语句,因此datasize的值为1。
371行,由于满足data_arg.read_write == I2C_SMBUS_WRITE这个条件,所以执行375行语句,将data_arg.data的第一个字节拷贝到temp变量中。
378行,条件不满足,略过。
先看387行,条件不满足,因此就剩下385行的i2c_smbus_xfer函数,下面看它在drivers/i2c/i2c-core.c中的定义:
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int protocol,
union i2c_smbus_data *data)
{
unsigned long orig_jiffies;
int try;
s32 res; flags &= I2C_M_TEN | I2C_CLIENT_PEC; if (adapter->algo->smbus_xfer) {
i2c_lock_adapter(adapter); /* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (res = , try = ; try <= adapter->retries; try++) {
res = adapter->algo->smbus_xfer(adapter, addr, flags,
read_write, command,
protocol, data);
if (res != -EAGAIN)
break;
if (time_after(jiffies,
orig_jiffies + adapter->timeout))
break;
}
i2c_unlock_adapter(adapter);
} else
res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
command, protocol, data); return res;
}
2076行,对于s3c6410的IIC控制器驱动来说,没有定义smbus_xfer函数,因此执行2093行的i2c_smbus_xfer_emulated函数,它的定义如下:
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data *data)
{
/* So we need to generate a series of msgs. In the case of writing, we
00001895 need to use only one message; when reading, we need two. We initialize
00001896 most things with sane defaults, to keep the code below somewhat
00001897 simpler. */
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+];
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+];
int num = read_write == I2C_SMBUS_READ ? : ;
struct i2c_msg msg[] = { { addr, flags, , msgbuf0 },
{ addr, flags | I2C_M_RD, , msgbuf1 }
};
int i;
u8 partial_pec = ;
int status; msgbuf0[] = command;
switch (size) {
case I2C_SMBUS_QUICK:
msg[].len = ;
/* Special case: The read/write field is used as data */
msg[].flags = flags | (read_write == I2C_SMBUS_READ ?
I2C_M_RD : );
num = ;
break;
case I2C_SMBUS_BYTE:
if (read_write == I2C_SMBUS_READ) {
/* Special case: only a read! */
msg[].flags = I2C_M_RD | flags;
num = ;
}
break;
case I2C_SMBUS_BYTE_DATA:
if (read_write == I2C_SMBUS_READ)
msg[].len = ;
else {
msg[].len = ;
msgbuf0[] = data->byte;
}
break;
case I2C_SMBUS_WORD_DATA:
if (read_write == I2C_SMBUS_READ)
msg[].len = ;
else {
msg[].len = ;
msgbuf0[] = data->word & 0xff;
msgbuf0[] = data->word >> ;
}
break;
case I2C_SMBUS_PROC_CALL:
num = ; /* Special case */
read_write = I2C_SMBUS_READ;
msg[].len = ;
msg[].len = ;
msgbuf0[] = data->word & 0xff;
msgbuf0[] = data->word >> ;
break;
case I2C_SMBUS_BLOCK_DATA:
if (read_write == I2C_SMBUS_READ) {
msg[].flags |= I2C_M_RECV_LEN;
msg[].len = ; /* block length will be added by
00001953 the underlying bus driver */
} else {
msg[].len = data->block[] + ;
if (msg[].len > I2C_SMBUS_BLOCK_MAX + ) {
dev_err(&adapter->dev,
"Invalid block write size %d\n",
data->block[]);
return -EINVAL;
}
for (i = ; i < msg[].len; i++)
msgbuf0[i] = data->block[i-];
}
break;
case I2C_SMBUS_BLOCK_PROC_CALL:
num = ; /* Another special case */
read_write = I2C_SMBUS_READ;
if (data->block[] > I2C_SMBUS_BLOCK_MAX) {
dev_err(&adapter->dev,
"Invalid block write size %d\n",
data->block[]);
return -EINVAL;
}
msg[].len = data->block[] + ;
for (i = ; i < msg[].len; i++)
msgbuf0[i] = data->block[i-];
msg[].flags |= I2C_M_RECV_LEN;
msg[].len = ; /* block length will be added by
00001980 the underlying bus driver */
break;
case I2C_SMBUS_I2C_BLOCK_DATA:
if (read_write == I2C_SMBUS_READ) {
msg[].len = data->block[];
} else {
msg[].len = data->block[] + ;
if (msg[].len > I2C_SMBUS_BLOCK_MAX + ) {
dev_err(&adapter->dev,
"Invalid block write size %d\n",
data->block[]);
return -EINVAL;
}
for (i = ; i <= data->block[]; i++)
msgbuf0[i] = data->block[i];
}
break;
default:
dev_err(&adapter->dev, "Unsupported transaction %d\n", size);
return -EOPNOTSUPP;
} i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
&& size != I2C_SMBUS_I2C_BLOCK_DATA);
if (i) {
/* Compute PEC if first message is a write */
if (!(msg[].flags & I2C_M_RD)) {
if (num == ) /* Write only */
i2c_smbus_add_pec(&msg[]);
else /* Write followed by read */
partial_pec = i2c_smbus_msg_pec(, &msg[]);
}
/* Ask for PEC if last message is a read */
if (msg[num-].flags & I2C_M_RD)
msg[num-].len++;
} status = i2c_transfer(adapter, msg, num);
if (status < )
return status; /* Check PEC if last message is a read */
if (i && (msg[num-].flags & I2C_M_RD)) {
status = i2c_smbus_check_pec(partial_pec, &msg[num-]);
if (status < )
return status;
} if (read_write == I2C_SMBUS_READ)
switch (size) {
case I2C_SMBUS_BYTE:
data->byte = msgbuf0[];
break;
case I2C_SMBUS_BYTE_DATA:
data->byte = msgbuf1[];
break;
case I2C_SMBUS_WORD_DATA:
case I2C_SMBUS_PROC_CALL:
data->word = msgbuf1[] | (msgbuf1[] << );
break;
case I2C_SMBUS_I2C_BLOCK_DATA:
for (i = ; i < data->block[]; i++)
data->block[i+] = msgbuf1[i];
break;
case I2C_SMBUS_BLOCK_DATA:
case I2C_SMBUS_BLOCK_PROC_CALL:
for (i = ; i < msgbuf1[] + ; i++)
data->block[i] = msgbuf1[i];
break;
}
return ;
}
函数很长,但是逻辑却很简单。1898行,定义msgbuf0数组用作写操作,里面放的是要写的数据,后面会看到。
1899行,定义msgbuf1数组用作读操作,这里讨论的是写操作,因此略过与读操作相关的内容。
1900行,因为read_write=I2C_SMBUS_WRITE,所以num的值为1。
1901行,定义2个message数组,同样,一个用作写,一个用作读。
1908行,msgbuf0[0] = command,即要写数据的地址。
1909行,switch(size),由于size的值为I2C_SMBUS_BYTE_DATA,所以1924行的条件成立。
1925行,条件不成立,因此直接到1928行,msg[0].len = 2,写一字节地址和写一个字节数据加起来刚好是2字节。1929行,msgbuf0[1] = data->byte,即要写入的数据。
2002至2015行,与错误检测相关的,略过它不会有什么影响。
先看2022至2050行,都是与读操作相关的,因此不说了。
再看2017行,调用i2c_transfer函数来进行传输,i2c_transfer函数的定义:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try; /* REVISIT the fault reporting model here is weak:
00001287 *
00001288 * - When we get an error after receiving N bytes from a slave,
00001289 * there is no way to report "N".
00001290 *
00001291 * - When we get a NAK after transmitting N bytes to a slave,
00001292 * there is no way to report "N" ... or to let the master
00001293 * continue executing the rest of this combined message, if
00001294 * that's the appropriate response.
00001295 *
00001296 * - When for example "num" is two and we successfully complete
00001297 * the first message but get an error part way through the
00001298 * second, it's unclear whether that should be reported as
00001299 * one (discarding status on the second message) or errno
00001300 * (discarding status on the first one).
00001301 */ if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = ; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_adapter(adap);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
i2c_lock_adapter(adap);
} /* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (ret = , try = ; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
i2c_unlock_adapter(adap); return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
一看,呆了眼,函数里面竟然有这么多注释。
1303行,master_xfer这个指针是有赋值的,因此执行if里面的语句。
1304至1311行,调试相关的,打印一些调试信息。
1313至1320行,锁住当前的适配器。
1324行,adap->retries的值在IIC控制器初始化的时候就设置为2,因此重试2次。
1325行,调用的是drivers/i2c/busses/i2c-s3c2410.c里的s3c24xx_i2c_xfer函数,它的定义:
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret; for (retry = ; retry < adap->retries; retry++) { ret = s3c24xx_i2c_doxfer(i2c, msgs, num); if (ret != -EAGAIN)
return ret; dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry); udelay();
} return -EREMOTEIO;
}
Linux设备驱动剖析之IIC(三)的更多相关文章
-
Linux设备驱动剖析之IIC(二)
953行,适配器的编号大于MAX_ID_MASK是不行的,MAX_ID_MASK是一个宏,展开后的值为61. 957至968行,关于管理小整形ID数的,没怎么了解,略过. 974行,调用i2c_reg ...
-
Linux设备驱动剖析之IIC(一)
写在前面 由于IIC总线只需要两根线就可以完成读写操作,而且通信协议简单,一条总线上可以挂载多个设备,因此被广泛使用.但是IIC总线有一个缺点,就是传输速率比较低.本文基于Linux-2.6.36版本 ...
-
Linux设备驱动剖析之IIC(四)
558行,又重试2次. 560行,调用s3c24xx_i2c_doxfer函数: static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, stru ...
-
linux设备驱动归纳总结(三):7.异步通知fasync【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-62725.html linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxx ...
-
linux设备驱动归纳总结(三):6.poll和sellct【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-61749.html linux设备驱动归纳总结(三):6.poll和sellct xxxxxxxxxx ...
-
linux设备驱动归纳总结(三):5.阻塞型IO实现【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-60025.html linux设备驱动归纳总结(三):5.阻塞型IO实现 xxxxxxxxxxxxxx ...
-
linux设备驱动归纳总结(三):4.ioctl的实现【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-59419.html linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简介: 虽 ...
-
linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现【转】
本文转自自:http://blog.chinaunix.net/uid-25014876-id-59418.html linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现 一. ...
-
linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-59417.html linux设备驱动归纳总结(三):2.字符型设备的操作open.close.rea ...
随机推荐
-
okhttp3的使用
http://www.qingpingshan.com/rjbc/az/110232.html 请求队列,post? 待定 http://www.07net01.com/2015/12/1017279 ...
-
anroid开发者专用vpn
http://android.vpn.ac.cn/
-
使用Yeoman搭建 AngularJS 应用 (4) —— 让我们搭建一个网页应用
在开发一个的网页传统工作流程中,你需要大量的时间去设置引用文件,下载依赖文件,并且手动的创建网页文件结构.Yeoman生成器将会帮助你完成这些.让我们安装一个AngularJS项目的生成器. 安装An ...
-
spring的beans.xml的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
-
[Swust OJ 404]--最小代价树(动态规划)
题目链接:http://acm.swust.edu.cn/problem/code/745255/ Time limit(ms): 1000 Memory limit(kb): 65535 Des ...
-
PhotoPickerDemo【PhotoPicker0.9.8的个性化修改以及使用(内部glide版本号是3.7.0)】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 本Demo使用的是PhotoPicker 0.9.8版本,属于比较旧的版本,里面集成的glide版本号是3.7.0.本篇文章主要是留 ...
-
ShowDoc上手
ShowDoc是什么 每当接手一个他人开发好的模块或者项目,看着那些没有写注释的代码,我们都无比抓狂.文档呢?!文档呢?!Show me the doc !! 程序员都很希望别人能写技术文档,而自己却 ...
-
【转】nvidia-smi 命令解读
nvidia-smi是linux下用来查看GPU使用情况的命令.具体的参数信息详见 原文:http://blog.csdn.net/sallyxyl1993/article/details/62220 ...
-
is与==
is和==的区别 1. id() 通过id()我们可以查看到⼀一个变量表示的值在内存中的地址. a1 = 100 b1 = 100 print(id(a1),id(b1)) #14071247240 ...
-
C++ primer ch6 函数基础-1
1.形参和实参:编译器并没有规定实参的求值顺序. 类似下面的代码,其行为是未定义的: ; printf("%d %d\n",++i,++i); 2.变量的初始化: 如果内置类型的变 ...