为什么地址线要20条?

2008年9月09日 22:39

*缘起

在学习内存寻址的时候,大多数资料都写到:由于要满足80861M寻址空间,Intel的聪明的工程师想出了“段式”方法,来把1M空间分成64K段地址空间来管理,也就出现了所谓的段地址+段内偏移量来达到寻址的目的。这样的原因看起来很理所应当,而且的确非常漂亮。但是到了32位机器时代的来临,甚至64位的出现,这个架构始终面临着向后兼容的痛苦,要“兼容”这个段式寻址。

但我要提出的疑问是: 为什么当时会出现80861M的寻址空间呢?64K的地址空间就那么不够用么?非“逼着”大家想出来“段式”这么一个“可怕的”历史结果。不知道大家对于后面引起的实模式和保护模式

在经过“谷歌”以后,找到一个可以勉强解释的理由:

原文地址:http://blog.chinaunix.net/u/21948/showart_239664.html

“但当上升到16位机后,Intel8086/8088CPU的设计由于当年IC集成技术和外封装及引脚技术的限制,不能超过40个引脚。但又感觉到8位机原来的地址寻址能力2^1664KB太少了,但直接增加到16的整数倍即令AB32位又是达不到的。故而只能把AB暂时增加4条成为20条。则 2^201MB的寻址能力已经增加了16倍。但此举却造成了AB20位和DB16位之间的矛盾,20位地址信息既无法在DB上传送,又无法在16位的CPU寄存器和内存单元中存放。于是应运而生就产生了CPU段结构的原理。”

这里有个理由看来是是合理的:40个引脚的限制。先看一下8086长什么样子吧。

找到8086datasheet,查找其引脚定义:

可以看到,AD0AD15是分时复用的16条总线。这样就造成了期望在此基础上能够尽可能大的寻址,那么其中一条可选的也是很好的选择就是采用16位段寄存器+16位段内位移实现最大的且相对简单的寻址方式。

这就解决了我心中的那个疑惑。:)

Assert

2008年9月05日 22:41

 

/* 以上为活跃c.c.l.c组内容乱写,版权所有,谢绝转载  */

/* ++ Assert.h */

/* 去掉头文件说明  */

/*
 *    ISO C99 Standard: 7.2 Diagnostics    <assert.h>
 */


#ifdef    _ASSERT_H

# undef    _ASSERT_H
# undef    assert
# undef __ASSERT_VOID_CAST

# ifdef    __USE_GNU
#  undef assert_perror
# endif

#endif /* assert.h    */

#define    _ASSERT_H    1
#include <features.h>

#if defined __cplusplus && __GNUC_PREREQ (2,95)
# define __ASSERT_VOID_CAST static_cast<void>
#else
# define __ASSERT_VOID_CAST (void)
#endif

/* void assert (int expression);

   If NDEBUG is defined, do nothing.
   If not, and EXPRESSION is zero, print an error message and abort.  */


#ifdef    NDEBUG

/* 为了当expr != 0 ,不输出任何内容 */
# define assert(expr)        (__ASSERT_VOID_CAST (0))

/* void assert_perror (int errnum);

   If NDEBUG is defined, do nothing.  If not, and ERRNUM is not zero, print an
   error message with the error text for ERRNUM and abort.
   (This is a GNU extension.) */


# ifdef    __USE_GNU
#  define assert_perror(errnum)    (__ASSERT_VOID_CAST (0))
# endif

/* 到这里可以看出如果定义了NDEBUG那么assert(expr) 这个宏就无效了  */

#else /* Not NDEBUG.  */

/* #ifdef    __cplusplus
 * # define __BEGIN_DECLS    extern "C" {
 * # define __END_DECLS    }
 * #else
 * # define __BEGIN_DECLS
 * # define __END_DECLS
 * #endif
 * 这个宏的目的就是给C++编译器看的,下面是C代码而不是C++的。实现如上
 */

__BEGIN_DECLS


/* 这里定义了两个宏, 这个assert是兼容标准C库,在assert.c会看到
 * __assert_fail()这个函数的实现。
 */

extern void __assert_fail (__const char *__assertion, __const char *__file,
               unsigned int __line, __const char *__function)
     __THROW __attribute__ ((__noreturn__));

/* 给GNU扩展用的  */
extern void __assert_perror_fail (int __errnum, __const char *__file,
                  unsigned int __line,
                  __const char *__function)
     __THROW __attribute__ ((__noreturn__));


/* 这个就是我们想看的宏  */
extern void __assert (const char *__assertion, const char *__file, int __line)
     __THROW __attribute__ ((__noreturn__));


/* 和上面的那个__BEGIN_DECLS对应  */
__END_DECLS


/* 这个是glibc的实现的宏  */

/* __STRING(expr)实现如下:
 * #define __STRING(x)    #x
 * 可以看到这个宏利用了宏定义的# 来达到传入表达式字符串的目的
 */


/* __ASSERT_FUNCTION实现如下:
 * # if defined __cplusplus ? __GNUC_PREREQ (2, 6) : __GNUC_PREREQ (2, 4)
 * #   define __ASSERT_FUNCTION    __PRETTY_FUNCTION__
 * # else
 * #  if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
 * #   define __ASSERT_FUNCTION    __func__
 * #  else
 * #   define __ASSERT_FUNCTION    ((__const char *) 0)
 * #  endif
 * # endif
 *  可以看到这个宏的目的就是替代函数名
 * 但__STDC_VERSION__ >= 199901L是啥意思?
 */

 
/* 如下这种实现方式是规范要求
 * 规范的例子如下:
 * #undef assert
 * #ifdef NDEBUG
 * #define  assert(ignore)  ((void)0)  10
 * #else
 * extern void _ _gripe(char *_Expr, char *_File,
 *      int _Line, const char *_Func);
 * #define assert(expr) \
 *   ((expr) ? (void)0 :\  15
 *  _ _gripe(#expr, _ _FILE_ _,_ _LINE_ _,_ _func_ _))
 * #endif
 从这个例子中我们也可以看到glibc的实现和规范的参考设计有哪些不同
 */

 
# define assert(expr)                            \
  ((expr)                                \
   ? __ASSERT_VOID_CAST (0)                        \
   : __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))


/* 下面这个是GNU扩展用的。  */
# ifdef    __USE_GNU
#  define assert_perror(errnum)                        \
  (!(errnum)                                \
   ? __ASSERT_VOID_CAST (0)                        \
   : __assert_perror_fail ((errnum), __FILE__, __LINE__, __ASSERT_FUNCTION))
# endif

/* Version 2.4 and later of GCC define a magical variable `__PRETTY_FUNCTION__'
   which contains the name of the function currently being defined.
   This is broken in G++ before version 2.6.
   C9x has a similar variable called __func__, but prefer the GCC one since
   it demangles C++ function names.  */

 
 
/* #if defined __GNUC__ && defined __GNUC_MINOR__
 * # define __GNUC_PREREQ(maj, min) \
 *     ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
 * #else
 * # define __GNUC_PREREQ(maj, min) 0
 * #endif
 * __GNUC_PREREQ就是一个方便看版本的宏,实现如上
 */

 
 
# if defined __cplusplus ? __GNUC_PREREQ (2, 6) : __GNUC_PREREQ (2, 4)
#   define __ASSERT_FUNCTION    __PRETTY_FUNCTION__
# else
#  if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
#   define __ASSERT_FUNCTION    __func__
#  else
#   define __ASSERT_FUNCTION    ((__const char *) 0)
#  endif
# endif

#endif /* NDEBUG.  */

/* -- Assert.h*/


/* 下面就是源文件 */

/*  ++ Assert.c */
/* Copyright (C) 1991,1994-1996,1998,2001,2002,2005
   Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */


#include <assert.h>
#include <libintl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysdep.h>
#include <unistd.h>

/* 从外部获得的执行的文件名,包含后缀  */
extern const char *__progname;

#ifdef USE_IN_LIBIO
# include <wchar.h>
# include <libio/iolibio.h>
# define fflush(s) INTUSE(_IO_fflush) (s)
#endif

/* This function, when passed a string containing an asserted
   expression, a filename, and a line number, prints a message
   on the standard error stream of the form:
       a.c:10: foobar: Assertion `a == b' failed.
   It then aborts program execution via a call to `abort'.  */


#ifdef FATAL_PREPARE_INCLUDE
# include FATAL_PREPARE_INCLUDE
#endif

#undef __assert_fail
void
__assert_fail (const char *assertion, const char *file, unsigned int line,
           const char *function)
{
  char *buf;

#ifdef FATAL_PREPARE
  FATAL_PREPARE;
#endif
/* __asprintf 的用法可以参考vasprintf用法基本相同:
 * 其实现在Vasprintf.c中
 */

   
   
  if (__asprintf (&buf, _("%s%s%s:%u: %s%sAssertion `%s' failed.\n"),
          __progname, __progname[0] ? ": " : "",
          file, line,
          function ? function : "", function ? ": " : "",
          assertion) >= 0)
    {
      /* Print the message.  */
/* __fxprintf的用法可以参考vfprintf,用法基本相同:  */
   
      (void) __fxprintf (NULL, "%s", buf);
/* 由于输出为标准错误输出,需要及时把错误刷上去          */
      (void) fflush (stderr);
      /* We have to free the buffer since the application might catch the
     SIGABRT.  */

/*     这里为什么必须要马上把这个buffer释放掉呢?  */
      free (buf);
    }
  else
    {
      /* At least print a minimal message.  */
/*         对于这种特殊的情况,那么需要特殊处理。
 *         那么这里就涉及到STDERR_FILENO和stderr有什么区别呢?
 *         STDERR_FILENO是标准文件输出,无缓冲IO,相当于直接调用系统调用,
 *         这也就解释了为什么要用_libc_write这个函数了。
 */

      static const char errstr[] = "Unexpected error.\n";
      __libc_write (STDERR_FILENO, errstr, sizeof (errstr) - 1);
    }
/* 为了程序终止的不同可能性,规范要求以abort来结束。
    这里使用的还是库函数,规范要求的abort个人理解应当是系统调用,那么我们在读到
    库函数abort的时候要留意一下是否有关于系统调用的实现规范。
    Abort库函数会发一个SIGABRT的异常信号,在库函数中属于环境函数
    */

  abort ();
}
hidden_def(__assert_fail)

/* -- Assert.c */



问题:

1,一般情况下宏都是要大写的,但为什么要把一些宏要做成小写呢? 难道就仅仅是为了“看起来”
像是一个函数而已么?

 

数据转换

2008年7月27日 22:00

  C语言中的数据转换指的是当不同类型的数据作为操作数时,某些运算符的操作会导致这些操作数的从一种类型转换为另一种类型。请注意这仅仅是数据的的类型转换,而不是数据的类型的转换。即: unsigned char Test = 256; 此时 Test的类型为unsigned char 类型,此类型最大值为255, 因此此时Test的值的类型会转换为int 类型。而Test的类型仍然为unsigned char类型。

  数据的值的转换按照转换方式可以大致分为隐式(自动)转换和显式(强制)转换。两种转换的结果都一样,区别在于是否有目的的转换(尤其对于指针转换而言)。【本文将不特别区别自动转换和强制转换】

  按照转换“方向”可以大致分为“升级”和“降级”。 升级即有较低级别的类型转换为较高级别的类型;降级则相反。

  数据转换的目的就是让不同类型的操作数的类型转换为同一类型。一般来说,自动转换会把“较窄”的操作数转换为“较宽”的操作数,进行的是“升级”操作,目的为不丢失数据信息。

  一般情况下,自动转换是“保险”的,但有些时候也不得不对自动转换有些“提防”。

  例如:

        char Test = -1;

  这里Test的值就一定是你看到的-1么? 这可不一定! 对于赋值操作来说也是要进行自动转换的! 这里还要涉及到的是对于char这种类型来说倒底是signed char 还是unsigned char 是和编译器的实现相关,也就是说 Test的值有可能是-1 (signed char);也有可能是255 ( unsigned char)。

  还有对于函数的返回值的自动转换:

        int fun(void)

        {

            ......

            return (int)value;

        }

  对上面的函数的返回值进行保存的时候,如果数据类型不同,也会发生数据转换,也会出现“意想不到”的情况。

 

  下面描述一下一般情况下的算术类型转换(由上至下)

  1,如果任何一个操作数为long double类型,则将另一个操作数转换为long double类型;

  2,如果任何一个操作数为double类型,则将另一个操作数转换为double类型;

  3,如果任何一个操作数为float类型,则将另一个操作数转换为float类型;

  4, 对于整型首先对两个操作数进行整形提升;

  5,如果任何一个操作数为 unsigned long int 类型,则将另一个操作数转换为 unsigned long int 类型;

  6,如果任何一个操作数为 long int 类型,另一个操作数为unsigned int类型。则要看编译器对于int类型,和long int类型哪个更宽,转换为其中更宽的那个。

  7,如果任何一个操作数为 long int 类型,则将另一个操作数转换为 long int 类型;

  8,如果任何一个操作数为 unsigned int 类型,则将另一个操作数转换为 unsigned int 类型;

  9,其余都转换为int 类型;

  还有一种情况就是指针的转换,这种转换为函数的实现提供了极大便利: 函数实现无需关心具体数据的类型。函数仅仅需要将调用者传入的指针都作为(void *)类型,转换为自己处理的类型。这个强转指针保证数据类型的任务交给函数的调用者。 这就给程序的调用和实现者都提出了很高的要求,就是对于指针所指对象的对齐提出很高要求,否则会很容易出现由于强转指针导致地址异常的情况。

 

参考:

1,《C程序设计语言》K&R 第二版

2,http://hi.baidu.com/supersu30/blog/item/862cae97cee97a6855fb967b.html 作者:风急云却静