Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Structured Programming


AUG92: STRUCTURED PROGRAMMING

Watch close, gang (and no giggling!). The boy is about to do something he doesn't do often: change his mind in public. Furthermore, the subject in question is one I thought I'd fight to the death in opposition: software patents. I think about it a lot, because software patents could still mean the end of the American edge in software development, especially for the small developer. I'm glad, at times, that I make my living writing about programming rather than actually doing it, because it's entirely possible that software patents could bring progress in programming technology to a screeching halt in another ten years.

It doesn't have to be that way.

Software patents could actually work for the small developer and not against him. The problem, in fact, is not with software patents at all, but with the patent system itself, as it is currently inflicted upon both hardware and software. This notion has been burning a hole in my pocket, so forgive me if I step back from Turbo Vision for half a column and explain.

What are Patents For?

People who have defended software patents from the outset claim that patents are absolutely necessary to ensure that an inventor's investment in time and effort is rewarded and not simply appropriated by someone else. Right on, brother. Trouble is, patents don't do that. They don't even come close. Patents currently ensure nothing except that the lawyers will get paid, and that the guy with the deepest pockets wins the war 85 or 90 percent of the time.

This is crazy. An occasional inventor wins the war against big infringers, like that chap who invented a certain kind of pliers and won big against Sears Roebuck some years ago. We regularly hear about the rare cases like this, and almost never about the inventors who run out of money halfway through an infringement case and simply give up, having not only lost their invention to infringement but frequently all their savings and credit fighting that infringement.

The current patent system is brutally stacked against individual inventors, who nonetheless petulantly defend it because they see it as all they've got. What they don't see (remarkably, if they call themselves inventors) is that the patent system could well be improved several thousand percent, simply by refocusing it on the jobs it is supposed to do: 1. Ensuring that research and invention is rewarded; 2. creating a business climate in which engineering art can progress briskly.

Stealth Patents

Currently, patents work diametrically against both of those stated aims. The biggest sin committed by our patent process against Job #1 is that patent applications are kept secret until patents are granted. People can invest a great deal of time and money independently inventing something, only to discover that someone else suddenly owns the idea, and everything they've done is lost, or else at the mercy of the new patent holder.

Such "stealth patents" rob the independent inventor of his or her research investment, and I've never heard a good explanation of why this must be so. Patent applications should be published immediately, so that persons independently researching the same concept have a chance to give up the fight before they lose their shirts, or else advance the engineering art by innovating around the published application. Also, making patent applications public immediately would allow prior art to be discovered and put forth by people outside the patent office before the patent is granted. Right now, prior art must be proven in court after a patent is granted, and all court fights are frightfully expensive, ultimately benefiting only our smug ruling class of trial lawyers.

Locking Out the Little Guy

The key problem with patents, however, is that they're used by large concerns to control markets and lock out small startup companies and individual inventors. IBM and other large companies frequently cross-license their patent portfolios at little or no cost to one another, in a sort of you-use-my-stuff and I'll-use-your-stuff deal. However, the small firm holding one minor patent has little to offer the Big Guys and must accept their royalty terms, usually a percentage for each patent licensed. Having to pay cash to license a complicated web of patents can make a product economically impossible. The large companies can freely use one another's patents as part of technology exchange licenses. The little guy is locked out. None of this should be surprising. Big companies like IBM and TI are awesomely inefficient, and they fear nothing so much as the hungry, low-overhead technology partnership working miracles in their jeans in a crufty part of South Phoenix.

Crazy Claims and Prior Art

I heard a talk given to hopeful inventors by an "invention consultant" some years ago, and it was an eye-opener. The most important thing about a patent, according to the consultant, was to draft the claims portion of the patent such that innovating around the patent was impossible. The idea is to make them as broad as possible without causing the examiners to reject the claims as unrealistic. In most cases, the chap went on, the patent office will let extremely broad claims pass--and once you have the patent, you can take infringers to court with the legal edge on your side.

If patent claims are intended to make further innovation impossible, they're working directly against the core of the patent idea, which is to make innovation rewarding and encourage the advance of engineering art. Standards for breadth of claims must be tightened, and existing patents with overly broad claims should be rescinded as fraudulent.

Prior art has proven a serious problem in software patents, in part because most software innovation has been protected as trade secrets rather than patents, and thus not published at all. But simple ignorance on the part of the patent office has allowed many absurd applications to obtain the legal strength of a granted patent.

Paul Heckel's ridiculous Zoomracks patent is the best example I've seen. Paul patented what amounts to a picture drawn on a text screen of something I used many years ago as a clerk at Xerox: the Ring-King Visible Records Rack, a steel plate covered with plastic card-holders that overlapped such that only the bottom quarter inch of each card was exposed. You could scan the rack for the name of the customer, then flip the card up and read the rest of the data. Zoomracks works pretty much this way. There's some additional gobbledegook about compressing data by yanking out vowels that looks a great deal like 1950s secretarial shorthand.

Should drawing a (marginal) screen picture of some device commonly used in the outside world be patentable? Is shorthand patentable?

Don't be silly. Yet the patent office let it pass.

So What's Obvious?

When our current patent system was designed, virtually all inventions were mechanical in nature, and relatively simple at that. Any reasonably educated person could tell if an invention was "obvious" or not. Well, technology has unfolded in countless ways, and the nature of obviousness is now something known best to regular practitioners in the field. This is doubly true today of software patents, which is an area that has not, until very recently, been subject to patents, and which most patent examiners know very little about.

What needs to be done is to convene volunteer examiner panels in each of numerous technological specialties, including software, and have the panels rule on whether applications are attempting to patent the obvious. Let the panels' decision be final--or at best, allow one appeal to a second panel convened of different individuals from the same field.

The Big Win

But above all else, bar nothing, the reform that could make our patent system realize its stated goals is to remove patent-holders' ability to control markets. Right now, if you hold a patent, you can offer it to some licensees but not others, charge different rates to different licensees, and all manner of market-manipulation mischief like that. My reform proposal is to create a system of competing rights collectives that would represent inventors and collect royalties on patents for them, taking a small fee. Inventors would be allowed to negotiate other licenses, but they would be required by law to make their inventions available for a standard, regulated maximum fee to anyone who wishes to license them.

The music business works a lot like this right now. You don't have to individually negotiate the right to perform a song in a bar with each songwriter. Instead, you buy a license from ASCAP, which distributes the royalties to participating songwriters. This works well, as judged by the enthusiasm of the songwriters for the system.

ASCAP has a huge war chest for prosecuting infringers, which they do relentlessly and remorselessly, as many small bar owners will surely attest. I think it's a great system. I think it would work phenomenally well for patents.

I envision it happening this way: Some portion of a product's net receipts would be earmarked for patent royalties. I think 15 percent would be about right, since (having done a little product developing myself) I think that no more than 15 percent of the manufacturer's portion of the value of any given product is pure innovation. Much of it is marketing, documentation, and simple implementation of public-domain art.

A concern wishing to license patents would register a product with one or more rights collectives, specifying which patents it is licensing. Those rights collectives would split the 15 percent equally, and be constrained from quibbling over which patent contributed "more" to the product's ultimate value. Such questions only enrich lawyers and do nothing for either inventors or the engineering art. A valid, paid-up license with a collective would protect the licensee from any patent litigation of any kind from that collective.

With the collectives working for them, doing the legal wrangling and crunching the paperwork, inventors could do what they do best. Better still, other inventors could build on the work of their fellows, and those with the most-licensed patents would get the most money. Inventors would be rewarded, and the progress of technology would go absolutely through the roof.

I would support this system. I would holler from the heights in favor of this system. I would even support nonobvious software patents under this system. I can foresee that IBM, TI, and Apple would oppose it, as would (of course) the lawyers' bloc.

Could it ever happen? Who knows? The USSR is history--weirder and tougher things have happened. We've got a long way to go. And knowing what I know of lawyers, I'm still damned glad I'm a writer and not a programmer.

A Mortgage-ing We Go

Enough of that. We're riding the whirlpool here, trying to make sense of Turbo Vision streams. I provided an overview of how streams work last month. This time, we'll start having a look at a practical example, a revision of HCALC .PAS that has the ability to write mortgage tables to a stream and read them back again.

My first job in adding streams to HCALC was, in fact, adding streams to the mortgage object itself. The revised MORTGAGE.PAS is given in Listing One (page 164). HCALC is a serious chunk of code and will have to wait until next month; there's plenty to talk about in the meantime.

I had to make three general mods to MORTGAGE.PAS:

  • I added the TMortgage type to the Turbo Vision object hierarchy by making it a child of TObject. I explained the reasons for this in detail last month: The very first field in any streamable object must be a pointer to that object's VMT. Since TObject is itself the child of no object and has a VMT pointer as its first field, all objects that descend from TObject will have the requisite VMT pointer as their first field.
  • I created a stream-registration record for TMortgage. It's called RMortgage, and it exists primarily to let the Turbo Pascal RTL know where a given object type's Store and Load methods exist in the code segment. Consider it a record in a behind-the-scenes index file that the RTL keeps of all its VMTs and stream access methods.
  • I added a Load and a Store method to the TMortgage object. The Load method must be a constructor, because what it does is very similar to what the quintessential constructor Init does: It builds an object on the heap. To do this, it uses information it reads from the stream. Init, by contrast, builds an object on the heap from information hardcoded into the constructor itself.
Store, on the other hand, is an ordinary method.

Looking Closely at TMortgage.Store

Storing an object out to a stream is relatively easy. All the data is right there, intact and accessible. As I show in the TMortgage. Store method, you simply blast out an object's fields one at a time, using the Write method belonging to TStream. Make sure you specify the stream when you call Write! That is, make sure the call is S. Write (if the name of your stream is S) rather than just Write.

One caution: If the object for which you're writing a Store method has a parent object with a Store method (TMortgage does not, because its parent, TObject, has no Store method), you must call the parent's Store method before beginning to send your own object's fields out to the stream. You'll see how this works next month, in the Store methods belonging to HCALC's TMortgage View objects.

The Write method needs the name of the field, and the number of bytes of data to be stored from that field to the stream. The best way to do this is to use the built-in SizeOffunction on the field's type specifier, like so:

  S.Write(Principal,SizeOf(Real));

Since type Real is six bytes long, this statement writes six bytes of data containing the field Principal out to stream instance S.

Only the last Write is vaguely tricky. If you remember from earlier discussions of the TMortgage type, the mortgage amortization table itself is a dynamically sized array on the heap. This allows you to have a 15-year, 30-year, or 247-month mortgage if you want, and not waste any heap space. The PaymentSize field is a long integer containing the total size in bytes of the Payments^ dynamic array. PaymentSize, passed as a parameter to S. Write, allows you to write only the exact amount of data to the stream to embrace the full length of the amortization table--and no more.

You'll note that although TMortgage has a pointer field named Payments, that pointer field is not written to the stream. Pointers are 32-bit addresses of memory locations on the heap. Writing them to disk is kind of pointless, because there's no promise that when you bring a pointer back from disk, it's going to point anywhere meaningful. We use the Payments pointer to help get the amortization table out to the stream, but that done, we no longer need Payments in the stream-writing process.

Getting Things Back from the Stream

What gets written out to the stream with Store gets read back from the stream with Load. The Load method is conceptually similar to Store. You use the stream's Read method to bring fields in from the stream, one by one, in the same order that they were written out with Store. Again, you must tell the Read method how many bytes of data to bring in from the stream with a second parameter.

TMortgage.Load doesn't attempt to read a value for Payments from the stream. We didn't write out anything for Payments to begin with. Instead, we allocate (with GetMem) just enough space on the heap to contain the table and store the address of that block of heapspace into Payments. We had previously read PaymentSize from the stream, containing the correct size of the amortization table. With Payments pointing to a correctly sized block of heapspace, we can load the amortization table onto the heap directly, with S.Read:

  S.Read(Payments^,PaymentSize);

That's all it takes to get the mortgage object itself to and from the stream. It may seem obvious, but for complicated objects with loads of fields, be careful to read fields back from a stream in the same order that they were written out.

More from the Confusion File

Which isn't to say that figuring it all out was easy. On page 157 of the Turbo Vision Guide, it says, "Turbo Vision registers all the standard objects, so you don't have to." That makes sense. Too bad that on page 163 it says, "The rule is simple and unforgiving: It's your responsibility to register every object type that your program will put onto a stream." Just my own? Or the standard ones too?

The answer (in case you were wondering, and if you've tried to do anything at all with streams then you've probably been wondering until your nose bled) is that page 163 has it right: Turbo Vision registers nothing for you. It defines registration records for all standard types (which is what I think the marginal note on page 157 was trying to say) but you have to call the RegisterType procedure for each standard type you intend to put out to a stream.

Note that this does not mean that you have to register the object types that you inherit from. TMortgageView inherits from TWindow, but I don't have to register TWindow--just the exact object types that must be streamed. The problem is that TWindow is a group, with a TFrame object attached to it through a pointer. TWindow's own Load and Store methods know how to deal with that frame object, so that you never have to bother worrying about writing the TFrame to the stream. However, you must still register TFrame yourself.

I got seriously messed over on this one. While trying to put a TMortgageView object out to a stream, I kept getting an I/O error code back from the stream. The code showed up as -6, which equates to the stPutError constant, indicating (see page 371 of the Turbo Vision Guide for the full st-series stream error-code listing) that I was trying to put an unregistered type onto the stream. I tried to register TView, TGroup, TWindow, and several other things before I remembered that every TWindow object comes with its own TFrame. Gakkh.

Do it Once and Then Stop!

All the standard types do, however, have predefined registration records, using a standard naming convention: Replace the T at the beginning of the standard type name with the letter R. Thus, the registration record for TFrame is RFrame. All of the standard types provided by Borland have object ID codes under 100, so any number over 100 is fair game.

Trying to register a type a second time will cause runtime error 212. This is a catchall error message that will trigger if any of several things go wrong while registering a type, but the two causes to watch out for are registering a type with the same ID code as an already-registered type, and registering an already-registered type. From TV's perspective, those two errors are identical, because it's the unique ID code in a registration record that defines a registration record as unique, not the name you give the record.

From your perspective, however, the first cause is generally choosing an ID code that some other type already uses, and the second is registering types in two or more different places in your application. To avoid confusion, gather all your registration calls into a single procedure. And take pains to note the registration ID codes of any third-party objects you incorporate into your applications. And if you yourself provide streamable objects to other programmers, complete with registration records, be sure to make it plain in comment headers or documentation what those ID codes are.

Closing in on It

That's my word budget for this session. We're actually closing in on the target, and it shouldn't take more than two more columns to peg streams reasonably well. Next month I'll provide the updated HCALC.PAS listing, and we'll speak of peer view pointers and other irritations. Month after that, well, it might well be time to evaluate Turbo Vision on a cost-benefit basis, and look around at other ways to skin the same cat.



_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]
<a name="01d1_0011">

{--------------------------------------------------------------------------}
{                     MORTGAGE                                               }
{ By Jeff Duntemann  --  From DDJ for August 1992                            }
{                        Last Updated 5/2/92                                 }
{ Major update: 3/25/92:                                                     }
{   Added all the rigmarole to make the TMortgage type streamable. It now    }
{   descends from TObject and uses the Objects unit. I also added the        }
{   registration record and the Load and Store methods.                      }
{--------------------------------------------------------------------------}

UNIT Mortgage;

INTERFACE

USES Objects;

TYPE
  Payment = RECORD      { One element in the amort. table. }
              PayPrincipal   : Real;
              PayInterest    : Real;
              PrincipalSoFar : Real;
              InterestSoFar  : Real;
              ExtraPrincipal : Real;
              Balance        : Real;
            END;
  PaymentArray   = ARRAY[1..2] OF Payment;  { Dynamic array! }
  PaymentPointer = ^PaymentArray;

  PMortgage = ^TMortgage;
  TMortgage =
    OBJECT(TObject)  { Must descend from TObject to be streamable   }
      Periods        : Integer;  { Number of periods in mortgage    }
      PeriodsPerYear : Integer;  { Number of periods in a year      }
      Principal      : Real;     { Amount of principal in cents     }
      Interest       : Real;     { Percentage of interest per *YEAR*}

      MonthlyPI   : Real;        { Monthly payment in cents         }
      Payments    : PaymentPointer;  { Array holding payments       }
      PaymentSize : LongInt;     { Size in bytes of payments array  }

      CONSTRUCTOR Init(StartPrincipal      : Real;
                       StartInterest       : Real;
                       StartPeriods        : Integer;
                       StartPeriodsPerYear : Integer);
      CONSTRUCTOR Load(VAR S : TStream);
      PROCEDURE SetNewInterestRate(NewRate : Real);
      PROCEDURE Recalc;
      PROCEDURE GetPayment(PaymentNumber   : Integer;
                           VAR ThisPayment : Payment);
      PROCEDURE ApplyExtraPrincipal(PaymentNumber : Integer;
                                    Extra         : Real);
      PROCEDURE RemoveExtraPrincipal(PaymentNumber : Integer);
      PROCEDURE Store(VAR S : TStream);
      DESTRUCTOR  Done; VIRTUAL;
    END;

CONST
  RMortgage : TStreamRec =
    (ObjType : 1200;
     VMTLink : Ofs(TypeOf(TMortgage)^);
     Load    : @TMortgage.Load;
     Store   : @TMortgage.Store);

IMPLEMENTATION

FUNCTION CalcPayment(Principal,InterestPerPeriod : Real;
                     NumberOfPeriods  : Integer) : Real;
VAR
  Factor : Real;
BEGIN
  Factor := EXP(-NumberOfPeriods * LN(1.0 + InterestPerPeriod));
  CalcPayment := Principal * InterestPerPeriod / (1.0 - Factor)
END;

CONSTRUCTOR TMortgage.Init(StartPrincipal      : Real;
                           StartInterest       : Real;
                           StartPeriods        : Integer;
                           StartPeriodsPerYear : Integer);
VAR
  I : Integer;
  InterestPerPeriod  : Real;
BEGIN
  { Set up all the initial state values: }
  Principal := StartPrincipal;
  Interest  := StartInterest;
  Periods   := StartPeriods;
  PeriodsPerYear := StartPeriodsPerYear;

  { Here we calculate the size that the payment array will occupy. }
  { We retain this because the number of payments may change...and }
  { we'll need to dispose of the array when the object is ditched: }
  PaymentSize := SizeOf(Payment) * Periods;

  { Allocate payment array on the heap: }
  GetMem(Payments,PaymentSize);

  { Initialize extra principal fields of payment array: }
  FOR I := 1 TO Periods DO
    Payments^[I].ExtraPrincipal := 0;
  Recalc;  { Calculate the amortization table }
END;

CONSTRUCTOR TMortgage.Load(VAR S : TStream);
BEGIN
  S.Read(Periods,       Sizeof(Integer));
  S.Read(PeriodsPerYear,SizeOf(Integer));
  S.Read(Principal,     SizeOf(Real));
  S.Read(Interest,      SizeOf(Real));
  S.Read(MonthlyPI,     SizeOf(Real));
  S.Read(PaymentSize,   SizeOf(LongInt));
  { Note that we *don't* try to read a pointer in from the stream. That would }
  { be meaningless; instead, we allocate heap space for the payments array    }
  { with GetMem and assign the returned pointer to Payments: }
  GetMem(Payments,PaymentSize);
  S.Read(Payments^,     PaymentSize);
END;

PROCEDURE TMortgage.Store(VAR S : TStream);
BEGIN
  S.Write(Periods,       Sizeof(Integer));
  S.Write(PeriodsPerYear,SizeOf(Integer));
  S.Write(Principal,     SizeOf(Real));
  S.Write(Interest,      SizeOf(Real));
  S.Write(MonthlyPI,     SizeOf(Real));
  { Note that we *don't* store the pointer to the payments array!   }
  { A pointer (i.e., a heap address) is meaningless written to disk.}
  S.Write(PaymentSize,   SizeOf(LongInt));
  S.Write(Payments^,     PaymentSize);
END;

PROCEDURE TMortgage.SetNewInterestRate(NewRate : Real);
BEGIN
  Interest := NewRate;
  Recalc;
END;

{ This method calculates the amortization table for the mortgage. }
{ The table is stored in the array pointed to by Payments.     }

PROCEDURE TMortgage.Recalc;
VAR
  I : Integer;
  RemainingPrincipal    : Real;
  PaymentCount          : Integer;
  InterestThisPeriod    : Real;
  InterestPerPeriod     : Real;
  HypotheticalPrincipal : Real;
BEGIN
  InterestPerPeriod := Interest/PeriodsPerYear;
  MonthlyPI := CalcPayment(Principal,
                           InterestPerPeriod,
                           Periods);
  { Round the monthly to cents: }
  MonthlyPI := int(MonthlyPI * 100.0 + 0.5) / 100.0;

  { Now generate the amortization table: }
  RemainingPrincipal := Principal;
  PaymentCount := 0;
  FOR I := 1 TO Periods DO
    BEGIN
      Inc(PaymentCount);
      { Calculate the interest this period and round it to cents:  }
      InterestThisPeriod :=
        Int((RemainingPrincipal * InterestPerPeriod) * 100 + 0.5) / 100.0;
      { Store values into payments array: }
      WITH Payments^[PaymentCount] DO
        BEGIN
          IF RemainingPrincipal = 0 THEN  { Loan's been paid off! }
            BEGIN
              PayInterest := 0;
              PayPrincipal := 0;
              Balance := 0;
            END
          ELSE
            BEGIN
              HypotheticalPrincipal :=
              MonthlyPI - InterestThisPeriod + ExtraPrincipal;
              IF HypotheticalPrincipal > RemainingPrincipal THEN
                PayPrincipal := RemainingPrincipal
              ELSE
                PayPrincipal := HypotheticalPrincipal;
              PayInterest  := InterestThisPeriod;
              RemainingPrincipal :=
                RemainingPrincipal - PayPrincipal; { Update running balance }
              Balance := RemainingPrincipal;
            END;
          { Update the cumulative interest and principal fields: }
          IF PaymentCount = 1 THEN
            BEGIN
              PrincipalSoFar := PayPrincipal;
              InterestSoFar  := PayInterest;
            END
          ELSE
            BEGIN
              PrincipalSoFar :=
                Payments^[PaymentCount-1].PrincipalSoFar + PayPrincipal;
              InterestSoFar  :=
                Payments^[PaymentCount-1].InterestSoFar + PayInterest;
            END;
        END;  { WITH }
    END;      { FOR }
END;          { TMortgage.Recalc }

PROCEDURE TMortgage.GetPayment(PaymentNumber   : Integer;
                               VAR ThisPayment : Payment);
BEGIN
  ThisPayment := Payments^[PaymentNumber];
END;

PROCEDURE TMortgage.ApplyExtraPrincipal(PaymentNumber : Integer;
                                        Extra         : Real);
BEGIN
  Payments^[PaymentNumber].ExtraPrincipal := Extra;
  Recalc;
END;

PROCEDURE TMortgage.RemoveExtraPrincipal(PaymentNumber : Integer);
BEGIN
  Payments^[PaymentNumber].ExtraPrincipal := 0.0;
  Recalc;
END;

DESTRUCTOR TMortgage.Done;
BEGIN
  FreeMem(Payments,PaymentSize);
END;

END.  { MORTGAGE }


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.